@tomorrowos/sdk 0.9.13 → 0.9.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lan-address.d.ts +6 -0
- package/dist/lan-address.d.ts.map +1 -0
- package/dist/lan-address.js +32 -0
- package/dist/server-status.js +3 -3
- package/dist/store/types.d.ts +7 -3
- package/dist/store/types.d.ts.map +1 -1
- package/dist/tomorrowos.d.ts +7 -3
- package/dist/tomorrowos.d.ts.map +1 -1
- package/dist/tomorrowos.js +19 -3
- package/package.json +1 -1
- package/templates/cms-starter/package.json +2 -2
- package/templates/cms-starter/public/index.html +6 -10
- package/templates/cms-starter/public/methods.js +31 -32
- package/templates/cms-starter/public/panel.css +1 -12
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lan-address.d.ts","sourceRoot":"","sources":["../src/lan-address.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,IAAI,CAcpD"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import os from "os";
|
|
2
|
+
/** Prefer common private LAN ranges for local CMS / player media base URLs. */
|
|
3
|
+
function scoreLanIpv4(ip) {
|
|
4
|
+
if (ip.startsWith("192.168."))
|
|
5
|
+
return 3;
|
|
6
|
+
if (ip.startsWith("10."))
|
|
7
|
+
return 2;
|
|
8
|
+
if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(ip))
|
|
9
|
+
return 2;
|
|
10
|
+
return 1;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Best-effort primary non-loopback IPv4 for local development.
|
|
14
|
+
* Returns null when no suitable interface is found.
|
|
15
|
+
*/
|
|
16
|
+
export function detectPrimaryLanIpv4() {
|
|
17
|
+
const nets = os.networkInterfaces();
|
|
18
|
+
const addresses = [];
|
|
19
|
+
for (const entries of Object.values(nets)) {
|
|
20
|
+
if (!entries)
|
|
21
|
+
continue;
|
|
22
|
+
for (const ent of entries) {
|
|
23
|
+
const family = ent.family;
|
|
24
|
+
const isV4 = family === "IPv4" || family === 4;
|
|
25
|
+
if (!isV4 || ent.internal)
|
|
26
|
+
continue;
|
|
27
|
+
addresses.push(ent.address);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
addresses.sort((a, b) => scoreLanIpv4(b) - scoreLanIpv4(a));
|
|
31
|
+
return addresses[0] ?? null;
|
|
32
|
+
}
|
package/dist/server-status.js
CHANGED
|
@@ -36,14 +36,14 @@ function resolveDatabaseProvider(env) {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
if (driver === "memory") {
|
|
39
|
-
return { provider: "memory", label: "
|
|
39
|
+
return { provider: "memory", label: "Database", configured: true };
|
|
40
40
|
}
|
|
41
41
|
if (driver === "sqlite" || !driver) {
|
|
42
|
-
return { provider: "sqlite", label: "
|
|
42
|
+
return { provider: "sqlite", label: "Database", configured: true };
|
|
43
43
|
}
|
|
44
44
|
return {
|
|
45
45
|
provider: driver || "unknown",
|
|
46
|
-
label:
|
|
46
|
+
label: "Database",
|
|
47
47
|
configured: hasUrl || driver === "sqlite" || driver === "memory"
|
|
48
48
|
};
|
|
49
49
|
}
|
package/dist/store/types.d.ts
CHANGED
|
@@ -16,12 +16,16 @@ export interface DeviceRegistryRecord {
|
|
|
16
16
|
/** CMS operator display name — survives unpair / re-pair. */
|
|
17
17
|
displayName?: string;
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Device-local daily screen on/off window (HH:mm, device clock).
|
|
21
|
+
* Off = display mute / power-save (Tizen panel mute or BrightSign HDMI power-save);
|
|
22
|
+
* device stays CMS-connected.
|
|
23
|
+
*/
|
|
20
24
|
export interface DeviceOnOffTimer {
|
|
21
25
|
enabled: boolean;
|
|
22
26
|
/** HH:mm — unmute / screen on */
|
|
23
27
|
turnOnAt: string;
|
|
24
|
-
/** HH:mm —
|
|
28
|
+
/** HH:mm — screen off (device stays CMS-connected) */
|
|
25
29
|
turnOffAt: string;
|
|
26
30
|
}
|
|
27
31
|
export interface PairedDeviceRecord {
|
|
@@ -39,7 +43,7 @@ export interface PairedDeviceRecord {
|
|
|
39
43
|
/** Latest device screenshot stored via uploaded_assets (local or Cloudinary). */
|
|
40
44
|
lastScreenshotAssetId?: string;
|
|
41
45
|
lastScreenshotCapturedAt?: string;
|
|
42
|
-
/**
|
|
46
|
+
/** Screen mute schedule (not device power off). */
|
|
43
47
|
onOffTimer?: DeviceOnOffTimer;
|
|
44
48
|
}
|
|
45
49
|
export interface PairedDeviceEntry {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wFAAwF;AACxF,MAAM,WAAW,oBAAoB;IACnC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"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"}
|
package/dist/tomorrowos.d.ts
CHANGED
|
@@ -66,7 +66,7 @@ export interface DeviceListItem {
|
|
|
66
66
|
url: string;
|
|
67
67
|
capturedAt: string;
|
|
68
68
|
} | null;
|
|
69
|
-
/**
|
|
69
|
+
/** Daily screen mute schedule (device stays connected). */
|
|
70
70
|
onOffTimer: DeviceOnOffTimer | null;
|
|
71
71
|
}
|
|
72
72
|
export interface DeviceLogEntry {
|
|
@@ -98,6 +98,8 @@ export declare class TomorrowOS extends EventEmitter {
|
|
|
98
98
|
private staticIndexFile;
|
|
99
99
|
/** Set when listen() starts — exposed to CMS panel for post-restart reconnect grace. */
|
|
100
100
|
private serverStartedAt;
|
|
101
|
+
/** Port passed to listen(); used for suggested LAN CMS URL. */
|
|
102
|
+
private listenPort;
|
|
101
103
|
constructor(options: TomorrowOSOptions);
|
|
102
104
|
/** Push all device assignments using latest playlist definitions (e.g. after reboot). */
|
|
103
105
|
pushLatestPolicyToDevice(deviceId: string): Promise<{
|
|
@@ -126,8 +128,8 @@ export declare class TomorrowOS extends EventEmitter {
|
|
|
126
128
|
/** List paired devices with live connection + timing fields for the CMS panel. */
|
|
127
129
|
listDevices(): Promise<DeviceListItem[]>;
|
|
128
130
|
/**
|
|
129
|
-
* Persist a daily on/off timer (
|
|
130
|
-
* pushed to the player when connected.
|
|
131
|
+
* Persist a daily on/off timer (screen mute / HDMI power-save). Saved even if
|
|
132
|
+
* offline; pushed to the player when connected.
|
|
131
133
|
*/
|
|
132
134
|
setDeviceOnOffTimer(deviceId: string, timerInput: unknown): Promise<{
|
|
133
135
|
deviceId: string;
|
|
@@ -165,6 +167,8 @@ export declare class TomorrowOS extends EventEmitter {
|
|
|
165
167
|
}>;
|
|
166
168
|
};
|
|
167
169
|
private sendCommandToSocket;
|
|
170
|
+
/** Best-effort LAN URL for local screens (e.g. http://192.168.1.10:3000). */
|
|
171
|
+
private getSuggestedCmsUrl;
|
|
168
172
|
listen(options: ListenOptions): http.Server;
|
|
169
173
|
private handleMediaUpload;
|
|
170
174
|
private storeUploadedMediaAsset;
|
package/dist/tomorrowos.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,IAAI,MAAM,MAAM,CAAC;AAYxB,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,EACV,gBAAgB,EAKhB,eAAe,EAEhB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"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"}
|
package/dist/tomorrowos.js
CHANGED
|
@@ -11,6 +11,7 @@ import { resolveBrandLogoPath, syncProjectAssetsToStaticRoot } from "./brand-ass
|
|
|
11
11
|
import { deleteCloudinaryAsset, resolveCloudinaryConfig, uploadBufferToCloudinary } from "./cloudinary-storage.js";
|
|
12
12
|
import { probeVideoDurationMs } from "./media-probe.js";
|
|
13
13
|
import { contentHashHex, storeUploadIfNeeded } from "./upload-storage.js";
|
|
14
|
+
import { detectPrimaryLanIpv4 } from "./lan-address.js";
|
|
14
15
|
import { buildServerStatus } from "./server-status.js";
|
|
15
16
|
function normalizeDevicePlatform(platform, system) {
|
|
16
17
|
const explicit = typeof platform === "string" ? platform.trim().toLowerCase() : "";
|
|
@@ -213,6 +214,8 @@ export class TomorrowOS extends EventEmitter {
|
|
|
213
214
|
staticIndexFile = "index.html";
|
|
214
215
|
/** Set when listen() starts — exposed to CMS panel for post-restart reconnect grace. */
|
|
215
216
|
serverStartedAt = null;
|
|
217
|
+
/** Port passed to listen(); used for suggested LAN CMS URL. */
|
|
218
|
+
listenPort = null;
|
|
216
219
|
constructor(options) {
|
|
217
220
|
super();
|
|
218
221
|
this.brand = options.brand;
|
|
@@ -410,8 +413,8 @@ export class TomorrowOS extends EventEmitter {
|
|
|
410
413
|
}));
|
|
411
414
|
}
|
|
412
415
|
/**
|
|
413
|
-
* Persist a daily on/off timer (
|
|
414
|
-
* pushed to the player when connected.
|
|
416
|
+
* Persist a daily on/off timer (screen mute / HDMI power-save). Saved even if
|
|
417
|
+
* offline; pushed to the player when connected.
|
|
415
418
|
*/
|
|
416
419
|
async setDeviceOnOffTimer(deviceId, timerInput) {
|
|
417
420
|
const id = String(deviceId || "").trim();
|
|
@@ -716,8 +719,17 @@ export class TomorrowOS extends EventEmitter {
|
|
|
716
719
|
}));
|
|
717
720
|
});
|
|
718
721
|
}
|
|
722
|
+
/** Best-effort LAN URL for local screens (e.g. http://192.168.1.10:3000). */
|
|
723
|
+
getSuggestedCmsUrl() {
|
|
724
|
+
const ip = detectPrimaryLanIpv4();
|
|
725
|
+
const port = this.listenPort;
|
|
726
|
+
if (!ip || port == null || !Number.isFinite(port))
|
|
727
|
+
return null;
|
|
728
|
+
return `http://${ip}:${port}`;
|
|
729
|
+
}
|
|
719
730
|
listen(options) {
|
|
720
731
|
const { port, host = "0.0.0.0", staticRoot, staticIndex } = options;
|
|
732
|
+
this.listenPort = port;
|
|
721
733
|
this.staticRoot = staticRoot ?? null;
|
|
722
734
|
if (this.staticRoot) {
|
|
723
735
|
const idx = (staticIndex ?? "index.html").trim().replace(/^[\\/]+/, "") || "index.html";
|
|
@@ -1088,7 +1100,11 @@ export class TomorrowOS extends EventEmitter {
|
|
|
1088
1100
|
store: this.store,
|
|
1089
1101
|
staticRoot: this.staticRoot
|
|
1090
1102
|
});
|
|
1091
|
-
|
|
1103
|
+
const suggestedCmsUrl = this.getSuggestedCmsUrl();
|
|
1104
|
+
sendJson(res, 200, {
|
|
1105
|
+
...report,
|
|
1106
|
+
...(suggestedCmsUrl ? { suggestedCmsUrl } : {})
|
|
1107
|
+
});
|
|
1092
1108
|
return;
|
|
1093
1109
|
}
|
|
1094
1110
|
if (req.method === "POST" && pathname === "/media/upload") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomorrowos/sdk",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.18",
|
|
4
4
|
"description": "TomorrowOS CMS server SDK - WebSocket transport, pairing, device commands, optional static CMS UI. Includes CLI (tomorrowos init / build) and starter templates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "my-cms",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.18",
|
|
4
4
|
"description": "CMS server on @tomorrowos/sdk. Add your UI (React, static files, etc.) alongside this server.",
|
|
5
5
|
"private": true,
|
|
6
6
|
"type": "module",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"build-player": "tomorrowos build --platform tizen"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@tomorrowos/sdk": "^0.9.
|
|
16
|
+
"@tomorrowos/sdk": "^0.9.18",
|
|
17
17
|
"dotenv": "^17.2.3",
|
|
18
18
|
"tsx": "^4.19.0"
|
|
19
19
|
},
|
|
@@ -66,15 +66,16 @@
|
|
|
66
66
|
<section class="card" id="cmsUrlSection">
|
|
67
67
|
<h2>CMS URL for screens</h2>
|
|
68
68
|
<p class="hint">
|
|
69
|
-
<strong>Local development only.</strong>
|
|
70
|
-
|
|
69
|
+
<strong>Local development only.</strong> Defaults to this PC’s LAN IP and listen port so TVs
|
|
70
|
+
can reach media (not localhost). Change and Save if the auto-detected address is wrong.
|
|
71
|
+
Hosted CMS (e.g. Replit) uses its public URL automatically — this section is hidden there.
|
|
71
72
|
</p>
|
|
72
73
|
<div class="row">
|
|
73
74
|
<input
|
|
74
75
|
id="cmsDeviceBaseUrl"
|
|
75
76
|
type="text"
|
|
76
77
|
style="flex: 1; min-width: 16rem"
|
|
77
|
-
placeholder="
|
|
78
|
+
placeholder="Detecting LAN address…"
|
|
78
79
|
/>
|
|
79
80
|
<button type="button" onclick="saveCmsDeviceBaseUrl()">Save</button>
|
|
80
81
|
</div>
|
|
@@ -185,12 +186,8 @@
|
|
|
185
186
|
<div class="modal-card">
|
|
186
187
|
<h2>On / off timer</h2>
|
|
187
188
|
<p class="hint" id="onOffTimerModalHint">
|
|
188
|
-
Daily
|
|
189
|
+
Daily on/off times follow the device clock. Off blacks the screen; the player stays powered and connected to the CMS.
|
|
189
190
|
</p>
|
|
190
|
-
<label class="field-label on-off-timer-enable">
|
|
191
|
-
<input type="checkbox" id="onOffTimerEnabled" />
|
|
192
|
-
Enable daily timer
|
|
193
|
-
</label>
|
|
194
191
|
<div class="on-off-timer-grid">
|
|
195
192
|
<label class="field-label">
|
|
196
193
|
Turn on (unmute)
|
|
@@ -201,7 +198,6 @@
|
|
|
201
198
|
<input type="time" id="onOffTimerTurnOffAt" value="18:00" />
|
|
202
199
|
</label>
|
|
203
200
|
</div>
|
|
204
|
-
<p class="hint" id="onOffTimerModalStatus"></p>
|
|
205
201
|
<div class="modal-actions">
|
|
206
202
|
<button type="button" id="onOffTimerSaveBtn" class="primary">Save</button>
|
|
207
203
|
<button type="button" data-close-on-off-timer-modal="1">Cancel</button>
|
|
@@ -233,7 +229,7 @@
|
|
|
233
229
|
<p class="hint">Current installed SDK version:</p>
|
|
234
230
|
<p class="update-sdk-version" id="updateSdkVersionLabel">Loading…</p>
|
|
235
231
|
<p class="hint">
|
|
236
|
-
To upgrade on
|
|
232
|
+
To upgrade on AI agent platforms (Replit, Vercel, etc.), paste this prompt into the Agent chat:
|
|
237
233
|
</p>
|
|
238
234
|
<pre class="update-sdk-prompt" id="updateSdkPromptText">Follow @tomorrowos/sdk REPLIT_UPGRADE.md to upgrade my CMS with the latest SDK.</pre>
|
|
239
235
|
<div class="modal-actions update-sdk-modal-actions">
|
|
@@ -190,6 +190,28 @@ function getExplicitLanMediaBase() {
|
|
|
190
190
|
return normalizeMediaBaseUrl(localStorage.getItem(PANEL_MEDIA_BASE_KEY) || "");
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Prefill CMS URL for screens from server-detected LAN IP:port when running on localhost
|
|
195
|
+
* and the operator has not saved a value yet. Auto-persists so thumbs/publish work without Save.
|
|
196
|
+
*/
|
|
197
|
+
function applyDefaultCmsUrlForScreens(suggested) {
|
|
198
|
+
if (!isLocalPanelHost(window.location.hostname)) return;
|
|
199
|
+
const normalized = normalizeMediaBaseUrl(suggested);
|
|
200
|
+
if (!normalized) return;
|
|
201
|
+
|
|
202
|
+
const cmsBaseInput = document.getElementById("cmsDeviceBaseUrl");
|
|
203
|
+
const saved = getExplicitLanMediaBase();
|
|
204
|
+
if (saved) {
|
|
205
|
+
if (cmsBaseInput && !String(cmsBaseInput.value || "").trim()) {
|
|
206
|
+
cmsBaseInput.value = saved;
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (cmsBaseInput) cmsBaseInput.value = normalized;
|
|
212
|
+
localStorage.setItem(PANEL_MEDIA_BASE_KEY, normalized);
|
|
213
|
+
}
|
|
214
|
+
|
|
193
215
|
function playlistHasRelativeMediaUrls(playlist) {
|
|
194
216
|
return (playlist?.items || []).some((item) => {
|
|
195
217
|
const url = String(item?.url || "").trim();
|
|
@@ -1280,8 +1302,8 @@ function renderDeviceCards() {
|
|
|
1280
1302
|
|
|
1281
1303
|
const timerBtn = document.createElement("button");
|
|
1282
1304
|
timerBtn.type = "button";
|
|
1283
|
-
timerBtn.textContent =
|
|
1284
|
-
timerBtn.title = "Daily screen on/off
|
|
1305
|
+
timerBtn.textContent = "Timer";
|
|
1306
|
+
timerBtn.title = "Daily screen on/off timer";
|
|
1285
1307
|
timerBtn.addEventListener("click", () => openOnOffTimerModal(device.deviceId));
|
|
1286
1308
|
|
|
1287
1309
|
const unpairBtn = document.createElement("button");
|
|
@@ -1298,9 +1320,7 @@ function renderDeviceCards() {
|
|
|
1298
1320
|
actions.appendChild(logsBtn);
|
|
1299
1321
|
actions.appendChild(screenshotBtn);
|
|
1300
1322
|
actions.appendChild(latestScreenshotBtn);
|
|
1301
|
-
|
|
1302
|
-
actions.appendChild(timerBtn);
|
|
1303
|
-
}
|
|
1323
|
+
actions.appendChild(timerBtn);
|
|
1304
1324
|
actions.appendChild(unpairBtn);
|
|
1305
1325
|
|
|
1306
1326
|
card.appendChild(header);
|
|
@@ -1793,12 +1813,6 @@ function closeScreenshotModal() {
|
|
|
1793
1813
|
modal?.classList.add("hidden");
|
|
1794
1814
|
}
|
|
1795
1815
|
|
|
1796
|
-
function isTizenDevice(device) {
|
|
1797
|
-
const platform = String(device?.platform || "").toLowerCase();
|
|
1798
|
-
const system = String(device?.system || "").toLowerCase();
|
|
1799
|
-
return platform.includes("tizen") || system.includes("tizen");
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
1816
|
function normalizeTimeInputValue(value, fallback) {
|
|
1803
1817
|
const raw = String(value || "").trim();
|
|
1804
1818
|
const match = /^(\d{1,2}):(\d{2})/.exec(raw);
|
|
@@ -1814,33 +1828,16 @@ let onOffTimerModalDeviceId = null;
|
|
|
1814
1828
|
function openOnOffTimerModal(deviceId) {
|
|
1815
1829
|
const device = devicesCache.find((d) => d.deviceId === deviceId);
|
|
1816
1830
|
if (!device) return;
|
|
1817
|
-
if (!isTizenDevice(device)) {
|
|
1818
|
-
alert("On/off timer is available for Samsung Tizen players only.");
|
|
1819
|
-
return;
|
|
1820
|
-
}
|
|
1821
1831
|
|
|
1822
1832
|
onOffTimerModalDeviceId = deviceId;
|
|
1823
1833
|
const modal = document.getElementById("onOffTimerModal");
|
|
1824
|
-
const hint = document.getElementById("onOffTimerModalHint");
|
|
1825
|
-
const status = document.getElementById("onOffTimerModalStatus");
|
|
1826
|
-
const enabledEl = document.getElementById("onOffTimerEnabled");
|
|
1827
1834
|
const onEl = document.getElementById("onOffTimerTurnOnAt");
|
|
1828
1835
|
const offEl = document.getElementById("onOffTimerTurnOffAt");
|
|
1829
|
-
if (!modal || !
|
|
1836
|
+
if (!modal || !onEl || !offEl) return;
|
|
1830
1837
|
|
|
1831
1838
|
const timer = device.onOffTimer || {};
|
|
1832
|
-
enabledEl.checked = Boolean(timer.enabled);
|
|
1833
1839
|
onEl.value = normalizeTimeInputValue(timer.turnOnAt, "06:00");
|
|
1834
1840
|
offEl.value = normalizeTimeInputValue(timer.turnOffAt, "18:00");
|
|
1835
|
-
if (hint) {
|
|
1836
|
-
hint.textContent =
|
|
1837
|
-
"Daily schedule on the device clock. Off uses Samsung panel mute (screen black); the player stays connected to the CMS.";
|
|
1838
|
-
}
|
|
1839
|
-
if (status) {
|
|
1840
|
-
status.textContent = device.connected
|
|
1841
|
-
? "Device is online — save will apply immediately."
|
|
1842
|
-
: "Device is offline — schedule will save and apply when it reconnects.";
|
|
1843
|
-
}
|
|
1844
1841
|
modal.classList.remove("hidden");
|
|
1845
1842
|
}
|
|
1846
1843
|
|
|
@@ -1854,14 +1851,13 @@ async function saveOnOffTimerModal() {
|
|
|
1854
1851
|
const deviceId = onOffTimerModalDeviceId;
|
|
1855
1852
|
if (!deviceId) return;
|
|
1856
1853
|
|
|
1857
|
-
const enabledEl = document.getElementById("onOffTimerEnabled");
|
|
1858
1854
|
const onEl = document.getElementById("onOffTimerTurnOnAt");
|
|
1859
1855
|
const offEl = document.getElementById("onOffTimerTurnOffAt");
|
|
1860
1856
|
const saveBtn = document.getElementById("onOffTimerSaveBtn");
|
|
1861
|
-
if (!
|
|
1857
|
+
if (!onEl || !offEl) return;
|
|
1862
1858
|
|
|
1863
1859
|
const onOffTimer = {
|
|
1864
|
-
enabled:
|
|
1860
|
+
enabled: true,
|
|
1865
1861
|
turnOnAt: normalizeTimeInputValue(onEl.value, "06:00"),
|
|
1866
1862
|
turnOffAt: normalizeTimeInputValue(offEl.value, "18:00")
|
|
1867
1863
|
};
|
|
@@ -2143,6 +2139,9 @@ async function fetchServerStatus() {
|
|
|
2143
2139
|
if (typeof data.sdkVersion === "string" && data.sdkVersion.trim()) {
|
|
2144
2140
|
cachedSdkVersion = data.sdkVersion.trim();
|
|
2145
2141
|
}
|
|
2142
|
+
if (typeof data.suggestedCmsUrl === "string") {
|
|
2143
|
+
applyDefaultCmsUrlForScreens(data.suggestedCmsUrl);
|
|
2144
|
+
}
|
|
2146
2145
|
renderServerStatus(data);
|
|
2147
2146
|
} catch (err) {
|
|
2148
2147
|
renderServerStatus({
|
|
@@ -829,22 +829,11 @@ button.danger {
|
|
|
829
829
|
width: min(94vw, 860px);
|
|
830
830
|
}
|
|
831
831
|
|
|
832
|
-
.on-off-timer-enable {
|
|
833
|
-
display: flex;
|
|
834
|
-
align-items: center;
|
|
835
|
-
gap: 0.5rem;
|
|
836
|
-
margin: 0.85rem 0 0.65rem;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
.on-off-timer-enable input {
|
|
840
|
-
width: auto;
|
|
841
|
-
margin: 0;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
832
|
.on-off-timer-grid {
|
|
845
833
|
display: grid;
|
|
846
834
|
grid-template-columns: 1fr 1fr;
|
|
847
835
|
gap: 0.75rem;
|
|
836
|
+
margin-top: 0.85rem;
|
|
848
837
|
}
|
|
849
838
|
|
|
850
839
|
@media (max-width: 520px) {
|