@tomorrowos/sdk 0.9.5 → 0.9.6
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/REPLIT_SETUP.md +1 -1
- package/dist/cloudinary-storage.d.ts +2 -0
- package/dist/cloudinary-storage.d.ts.map +1 -1
- package/dist/cloudinary-storage.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/server-status.d.ts +30 -0
- package/dist/server-status.d.ts.map +1 -0
- package/dist/server-status.js +244 -0
- package/dist/tomorrowos.d.ts +1 -0
- package/dist/tomorrowos.d.ts.map +1 -1
- package/dist/tomorrowos.js +9 -0
- package/package.json +1 -1
- package/templates/cms-starter/package.json +2 -2
- package/templates/cms-starter/public/index.html +10 -0
- package/templates/cms-starter/public/methods.js +163 -0
- package/templates/cms-starter/public/panel.css +147 -0
package/REPLIT_SETUP.md
CHANGED
|
@@ -17,4 +17,6 @@ export declare function uploadBufferToCloudinary(body: Buffer, options: {
|
|
|
17
17
|
filename: string;
|
|
18
18
|
}): Promise<CloudinaryUploadResult>;
|
|
19
19
|
export declare function deleteCloudinaryAsset(config: CloudinaryStorageConfig, publicId: string, resourceType?: string): Promise<void>;
|
|
20
|
+
/** Lightweight credential / reachability check for CMS status UI. */
|
|
21
|
+
export declare function pingCloudinary(config: CloudinaryStorageConfig): Promise<void>;
|
|
20
22
|
//# sourceMappingURL=cloudinary-storage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudinary-storage.d.ts","sourceRoot":"","sources":["../src/cloudinary-storage.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,uBAAuB,GAAG,IAAI,CAoBhC;AAWD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IACP,MAAM,EAAE,uBAAuB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACA,OAAO,CAAC,sBAAsB,CAAC,CAsCjC;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,uBAAuB,EAC/B,QAAQ,EAAE,MAAM,EAChB,YAAY,SAAU,GACrB,OAAO,CAAC,IAAI,CAAC,CAKf"}
|
|
1
|
+
{"version":3,"file":"cloudinary-storage.d.ts","sourceRoot":"","sources":["../src/cloudinary-storage.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,uBAAuB,GAAG,IAAI,CAoBhC;AAWD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IACP,MAAM,EAAE,uBAAuB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACA,OAAO,CAAC,sBAAsB,CAAC,CAsCjC;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,uBAAuB,EAC/B,QAAQ,EAAE,MAAM,EAChB,YAAY,SAAU,GACrB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,qEAAqE;AACrE,wBAAsB,cAAc,CAClC,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAGf"}
|
|
@@ -68,3 +68,8 @@ export async function deleteCloudinaryAsset(config, publicId, resourceType = "im
|
|
|
68
68
|
resource_type: resourceType
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
|
+
/** Lightweight credential / reachability check for CMS status UI. */
|
|
72
|
+
export async function pingCloudinary(config) {
|
|
73
|
+
configureCloudinary(config);
|
|
74
|
+
await cloudinary.api.ping();
|
|
75
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { TomorrowOS } from "./tomorrowos.js";
|
|
2
2
|
export type { DeviceListItem, DeviceScreenshotInfo, ListenOptions, TomorrowOSBrand, TomorrowOSOptions } from "./tomorrowos.js";
|
|
3
|
+
export type { ConnectorState, ConnectorStatus, ServerStatusReport, StatusBlocker } from "./server-status.js";
|
|
4
|
+
export { buildServerStatus } from "./server-status.js";
|
|
3
5
|
export type { DevicePlaylistAssignment, DeviceRegistryRecord, PairedDeviceRecord, PendingCodeEntry, PendingCodeRecord, DeviceRegistryEntry, PlaylistItemRecord, PlaylistSchedule, PublishedPlaylistSnapshot, StoredPlaylist, TomorrowOSDataSnapshot, TomorrowOSMigratableStore, TomorrowOSStore } from "./store/types.js";
|
|
4
6
|
export { PlaylistCatalog } from "./playlist-catalog.js";
|
|
5
7
|
export type { BuiltDevicePolicy, SavePlaylistInput } from "./playlist-catalog.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,aAAa,EACd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,yBAAyB,EACzB,cAAc,EACd,sBAAsB,EACtB,yBAAyB,EACzB,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EACL,2BAA2B,EAC3B,gCAAgC,EAChC,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,wBAAwB,EACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,YAAY,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,4BAA4B,EAC5B,qBAAqB,EACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { TomorrowOS } from "./tomorrowos.js";
|
|
2
|
+
export { buildServerStatus } from "./server-status.js";
|
|
2
3
|
export { PlaylistCatalog } from "./playlist-catalog.js";
|
|
3
4
|
export { DEFAULT_PAIRING_CODE_SECRET, generateDeterministicPairingCode, generateRandomPairingCode, isValidPairingCodeFormat, normalizePairingCode, PAIRING_CODE_ALPHABET, PAIRING_CODE_LENGTH, resolvePairingCodeSecret } from "./pairing-code.js";
|
|
4
5
|
export { MemoryStore } from "./store/memory-store.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { TomorrowOSStore } from "./store/types.js";
|
|
2
|
+
export type ConnectorState = "ok" | "warn" | "error" | "missing";
|
|
3
|
+
export interface ConnectorStatus {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
provider?: string;
|
|
7
|
+
state: ConnectorState;
|
|
8
|
+
detail: string;
|
|
9
|
+
}
|
|
10
|
+
export interface StatusBlocker {
|
|
11
|
+
connectorId: string;
|
|
12
|
+
title: string;
|
|
13
|
+
message: string;
|
|
14
|
+
fixHint: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ServerStatusReport {
|
|
17
|
+
status: "success";
|
|
18
|
+
overall: "ok" | "degraded" | "blocked";
|
|
19
|
+
checkedAt: string;
|
|
20
|
+
connectors: ConnectorStatus[];
|
|
21
|
+
blockers: StatusBlocker[];
|
|
22
|
+
}
|
|
23
|
+
export interface BuildServerStatusOptions {
|
|
24
|
+
store: TomorrowOSStore;
|
|
25
|
+
staticRoot?: string | null;
|
|
26
|
+
env?: NodeJS.ProcessEnv;
|
|
27
|
+
probeTimeoutMs?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function buildServerStatus(options: BuildServerStatusOptions): Promise<ServerStatusReport>;
|
|
30
|
+
//# sourceMappingURL=server-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-status.d.ts","sourceRoot":"","sources":["../src/server-status.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,IAAI,GAAG,UAAU,GAAG,SAAS,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,eAAe,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAyQD,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CA4B7B"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { pingCloudinary, resolveCloudinaryConfig } from "./cloudinary-storage.js";
|
|
4
|
+
function isReplitHost(env) {
|
|
5
|
+
return !!(env.REPL_ID ||
|
|
6
|
+
env.REPLIT_DEV_DOMAIN ||
|
|
7
|
+
env.REPLIT_DEPLOYMENT ||
|
|
8
|
+
env.REPL_SLUG);
|
|
9
|
+
}
|
|
10
|
+
function resolveDatabaseProvider(env) {
|
|
11
|
+
const driver = String(env.TOMORROWOS_STORE || "").trim().toLowerCase();
|
|
12
|
+
const hasUrl = !!(env.SUPABASE_URL || env.DATABASE_URL);
|
|
13
|
+
if (driver === "supabase" || (!driver && env.SUPABASE_URL)) {
|
|
14
|
+
return {
|
|
15
|
+
provider: "supabase",
|
|
16
|
+
label: "Supabase",
|
|
17
|
+
configured: !!env.SUPABASE_URL || !!env.DATABASE_URL
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (driver === "postgres" || (!driver && hasUrl && !env.SUPABASE_URL)) {
|
|
21
|
+
return {
|
|
22
|
+
provider: "postgres",
|
|
23
|
+
label: "Postgres",
|
|
24
|
+
configured: hasUrl
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (driver === "memory") {
|
|
28
|
+
return { provider: "memory", label: "Memory Store", configured: true };
|
|
29
|
+
}
|
|
30
|
+
if (driver === "sqlite" || !driver) {
|
|
31
|
+
return { provider: "sqlite", label: "SQLite", configured: true };
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
provider: driver || "unknown",
|
|
35
|
+
label: driver ? `Store (${driver})` : "Database",
|
|
36
|
+
configured: hasUrl || driver === "sqlite" || driver === "memory"
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
async function withTimeout(promise, timeoutMs, label) {
|
|
40
|
+
let timer;
|
|
41
|
+
try {
|
|
42
|
+
return await Promise.race([
|
|
43
|
+
promise,
|
|
44
|
+
new Promise((_, reject) => {
|
|
45
|
+
timer = setTimeout(() => reject(new Error(`${label} probe timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
46
|
+
})
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
if (timer)
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function probeDatabase(store, env, timeoutMs) {
|
|
55
|
+
const meta = resolveDatabaseProvider(env);
|
|
56
|
+
const wantsRemote = meta.provider === "supabase" || meta.provider === "postgres";
|
|
57
|
+
if (wantsRemote && !meta.configured) {
|
|
58
|
+
return {
|
|
59
|
+
id: "database",
|
|
60
|
+
label: meta.label,
|
|
61
|
+
provider: meta.provider,
|
|
62
|
+
state: "missing",
|
|
63
|
+
detail: "Connection string not set. Add SUPABASE_URL (preferred) or DATABASE_URL."
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
await withTimeout(store.listPairedDevices(), timeoutMs, meta.label);
|
|
68
|
+
if (meta.provider === "memory") {
|
|
69
|
+
return {
|
|
70
|
+
id: "database",
|
|
71
|
+
label: meta.label,
|
|
72
|
+
provider: meta.provider,
|
|
73
|
+
state: "warn",
|
|
74
|
+
detail: "In-memory only — data is lost on restart."
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (meta.provider === "sqlite" && isReplitHost(env)) {
|
|
78
|
+
return {
|
|
79
|
+
id: "database",
|
|
80
|
+
label: meta.label,
|
|
81
|
+
provider: meta.provider,
|
|
82
|
+
state: "warn",
|
|
83
|
+
detail: "SQLite works for demos; pairings may reset on rebuild. Prefer Supabase for production."
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
id: "database",
|
|
88
|
+
label: meta.label,
|
|
89
|
+
provider: meta.provider,
|
|
90
|
+
state: "ok",
|
|
91
|
+
detail: `${meta.label} reachable`
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
96
|
+
return {
|
|
97
|
+
id: "database",
|
|
98
|
+
label: meta.label,
|
|
99
|
+
provider: meta.provider,
|
|
100
|
+
state: "error",
|
|
101
|
+
detail: message
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function probeMedia(staticRoot, env, timeoutMs) {
|
|
106
|
+
const cloudinary = resolveCloudinaryConfig(env);
|
|
107
|
+
if (cloudinary) {
|
|
108
|
+
try {
|
|
109
|
+
await withTimeout(pingCloudinary(cloudinary), timeoutMs, "Cloudinary");
|
|
110
|
+
return {
|
|
111
|
+
id: "media",
|
|
112
|
+
label: "Media Server",
|
|
113
|
+
provider: "cloudinary",
|
|
114
|
+
state: "ok",
|
|
115
|
+
detail: `Cloudinary (${cloudinary.cloudName}) reachable`
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
120
|
+
return {
|
|
121
|
+
id: "media",
|
|
122
|
+
label: "Media Server",
|
|
123
|
+
provider: "cloudinary",
|
|
124
|
+
state: "error",
|
|
125
|
+
detail: message
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const hasPartialCloudinary = !!(env.CLOUDINARY_CLOUD_NAME ||
|
|
130
|
+
env.CLOUDINARY_API_KEY ||
|
|
131
|
+
env.CLOUDINARY_API_SECRET);
|
|
132
|
+
if (hasPartialCloudinary) {
|
|
133
|
+
return {
|
|
134
|
+
id: "media",
|
|
135
|
+
label: "Media Server",
|
|
136
|
+
provider: "cloudinary",
|
|
137
|
+
state: "error",
|
|
138
|
+
detail: "Cloudinary config incomplete — set CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, and CLOUDINARY_API_SECRET."
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const provider = isReplitHost(env) ? "replit-object-storage" : "local";
|
|
142
|
+
const providerLabel = provider === "replit-object-storage"
|
|
143
|
+
? "Replit Object Storage / local uploads"
|
|
144
|
+
: "Local uploads";
|
|
145
|
+
if (!staticRoot) {
|
|
146
|
+
return {
|
|
147
|
+
id: "media",
|
|
148
|
+
label: "Media Server",
|
|
149
|
+
provider,
|
|
150
|
+
state: "missing",
|
|
151
|
+
detail: "No Cloudinary and staticRoot is unset — media uploads are unavailable."
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const uploadsDir = path.join(path.resolve(staticRoot), "uploads");
|
|
155
|
+
try {
|
|
156
|
+
await fs.mkdir(uploadsDir, { recursive: true });
|
|
157
|
+
await fs.access(uploadsDir);
|
|
158
|
+
return {
|
|
159
|
+
id: "media",
|
|
160
|
+
label: "Media Server",
|
|
161
|
+
provider,
|
|
162
|
+
state: "warn",
|
|
163
|
+
detail: `${providerLabel} writable. Uploads may not survive rebuilds — Cloudinary recommended.`
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
168
|
+
return {
|
|
169
|
+
id: "media",
|
|
170
|
+
label: "Media Server",
|
|
171
|
+
provider,
|
|
172
|
+
state: "error",
|
|
173
|
+
detail: `Uploads directory not writable (${uploadsDir}): ${message}`
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function buildBlockers(connectors) {
|
|
178
|
+
const blockers = [];
|
|
179
|
+
for (const c of connectors) {
|
|
180
|
+
if (c.id === "database" && (c.state === "missing" || c.state === "error")) {
|
|
181
|
+
blockers.push({
|
|
182
|
+
connectorId: c.id,
|
|
183
|
+
title: `${c.label} not connected`,
|
|
184
|
+
message: c.detail,
|
|
185
|
+
fixHint: c.state === "missing"
|
|
186
|
+
? "Paste your Supabase Postgres connection string as Secret SUPABASE_URL, set TOMORROWOS_STORE=supabase, then restart the CMS."
|
|
187
|
+
: "Check SUPABASE_URL / network / SSL (DATABASE_SSL=true). After Publish, confirm the live URL can reach Supabase."
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (c.id === "media" && c.state === "error") {
|
|
191
|
+
blockers.push({
|
|
192
|
+
connectorId: c.id,
|
|
193
|
+
title: "Media storage not ready",
|
|
194
|
+
message: c.detail,
|
|
195
|
+
fixHint: c.provider === "cloudinary"
|
|
196
|
+
? "Fix Cloudinary Secrets (CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET) and restart."
|
|
197
|
+
: "Enable Object Storage for public/uploads, or configure Cloudinary for durable media URLs."
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (c.id === "media" && c.state === "missing") {
|
|
201
|
+
blockers.push({
|
|
202
|
+
connectorId: c.id,
|
|
203
|
+
title: "Media storage not configured",
|
|
204
|
+
message: c.detail,
|
|
205
|
+
fixHint: "Set Cloudinary Secrets, or ensure the CMS listens with staticRoot pointing at public/ (uploads folder)."
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return blockers;
|
|
210
|
+
}
|
|
211
|
+
function overallFrom(connectors) {
|
|
212
|
+
const hardFail = connectors.some((c) => (c.id === "database" || c.id === "media") &&
|
|
213
|
+
(c.state === "error" || c.state === "missing"));
|
|
214
|
+
if (hardFail)
|
|
215
|
+
return "blocked";
|
|
216
|
+
if (connectors.some((c) => c.state === "warn"))
|
|
217
|
+
return "degraded";
|
|
218
|
+
return "ok";
|
|
219
|
+
}
|
|
220
|
+
export async function buildServerStatus(options) {
|
|
221
|
+
const env = options.env ?? process.env;
|
|
222
|
+
const timeoutMs = options.probeTimeoutMs ?? 5000;
|
|
223
|
+
const server = {
|
|
224
|
+
id: "server",
|
|
225
|
+
label: "Server",
|
|
226
|
+
provider: "tomorrowos",
|
|
227
|
+
state: "ok",
|
|
228
|
+
detail: "CMS process is running"
|
|
229
|
+
};
|
|
230
|
+
const [database, media] = await Promise.all([
|
|
231
|
+
probeDatabase(options.store, env, timeoutMs),
|
|
232
|
+
probeMedia(options.staticRoot, env, timeoutMs)
|
|
233
|
+
]);
|
|
234
|
+
const connectors = [server, database, media];
|
|
235
|
+
const blockers = buildBlockers(connectors);
|
|
236
|
+
const overall = overallFrom(connectors);
|
|
237
|
+
return {
|
|
238
|
+
status: "success",
|
|
239
|
+
overall,
|
|
240
|
+
checkedAt: new Date().toISOString(),
|
|
241
|
+
connectors,
|
|
242
|
+
blockers
|
|
243
|
+
};
|
|
244
|
+
}
|
package/dist/tomorrowos.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { EventEmitter } from "events";
|
|
|
2
2
|
import http from "http";
|
|
3
3
|
import { PlaylistCatalog, type BuiltDevicePolicy } from "./playlist-catalog.js";
|
|
4
4
|
import type { TomorrowOSStore } from "./store/types.js";
|
|
5
|
+
export type { ConnectorState, ConnectorStatus, ServerStatusReport, StatusBlocker } from "./server-status.js";
|
|
5
6
|
export interface TomorrowOSBrand {
|
|
6
7
|
name?: string;
|
|
7
8
|
cmsEndpoint?: string;
|
package/dist/tomorrowos.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"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
|
|
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 { buildServerStatus } from "./server-status.js";
|
|
14
15
|
function normalizeDevicePlatform(platform, system) {
|
|
15
16
|
const explicit = typeof platform === "string" ? platform.trim().toLowerCase() : "";
|
|
16
17
|
if (explicit)
|
|
@@ -1024,6 +1025,14 @@ export class TomorrowOS extends EventEmitter {
|
|
|
1024
1025
|
res.end(JSON.stringify(this.brand));
|
|
1025
1026
|
return;
|
|
1026
1027
|
}
|
|
1028
|
+
if (req.method === "GET" && pathname === "/status") {
|
|
1029
|
+
const report = await buildServerStatus({
|
|
1030
|
+
store: this.store,
|
|
1031
|
+
staticRoot: this.staticRoot
|
|
1032
|
+
});
|
|
1033
|
+
sendJson(res, 200, report);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1027
1036
|
if (req.method === "POST" && pathname === "/media/upload") {
|
|
1028
1037
|
await this.handleMediaUpload(req, res, url);
|
|
1029
1038
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomorrowos/sdk",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.6",
|
|
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.6",
|
|
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.6",
|
|
17
17
|
"dotenv": "^17.2.3",
|
|
18
18
|
"tsx": "^4.19.0"
|
|
19
19
|
},
|
|
@@ -27,6 +27,16 @@
|
|
|
27
27
|
</aside>
|
|
28
28
|
|
|
29
29
|
<main class="panel-main">
|
|
30
|
+
<section class="card server-status-card" id="serverStatusSection" aria-live="polite">
|
|
31
|
+
<div class="server-status-header">
|
|
32
|
+
<h2>Server status</h2>
|
|
33
|
+
<button type="button" id="serverStatusRefreshBtn" title="Refresh status">Refresh</button>
|
|
34
|
+
</div>
|
|
35
|
+
<p class="hint server-status-overall" id="serverStatusOverall">Checking connectors…</p>
|
|
36
|
+
<ul class="server-status-list" id="serverStatusList"></ul>
|
|
37
|
+
<div id="serverStatusBlockers" class="server-status-blockers hidden"></div>
|
|
38
|
+
</section>
|
|
39
|
+
|
|
30
40
|
<section class="card">
|
|
31
41
|
<h2>Pair device</h2>
|
|
32
42
|
<p class="hint">Enter the 6-character code on the screen (A–Z or 0–9).</p>
|
|
@@ -27,6 +27,7 @@ let editingDeviceNameId = null;
|
|
|
27
27
|
let editingDeviceNameValue = "";
|
|
28
28
|
|
|
29
29
|
let devicePollTimer = null;
|
|
30
|
+
let serverStatusTimer = null;
|
|
30
31
|
/** @type {number|null} CMS server boot time (ms) from GET /devices. */
|
|
31
32
|
let serverStartedAtMs = null;
|
|
32
33
|
/** @type {ReturnType<typeof setTimeout>|null} */
|
|
@@ -1857,12 +1858,169 @@ async function downloadMediaAsset(item) {
|
|
|
1857
1858
|
openDownloadFailedModal(lastError);
|
|
1858
1859
|
}
|
|
1859
1860
|
|
|
1861
|
+
function connectorStateLabel(state) {
|
|
1862
|
+
if (state === "ok") return "OK";
|
|
1863
|
+
if (state === "warn") return "Warning";
|
|
1864
|
+
if (state === "missing") return "Not set";
|
|
1865
|
+
return "Error";
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
function overallStatusCopy(overall) {
|
|
1869
|
+
if (overall === "ok") return "All connectors look healthy.";
|
|
1870
|
+
if (overall === "degraded") {
|
|
1871
|
+
return "CMS is running with warnings — review media or database durability below.";
|
|
1872
|
+
}
|
|
1873
|
+
if (overall === "blocked") {
|
|
1874
|
+
return "Setup incomplete — fix the blockers below so pairing and media work reliably.";
|
|
1875
|
+
}
|
|
1876
|
+
return "Checking connectors…";
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
function renderServerStatus(report) {
|
|
1880
|
+
const section = document.getElementById("serverStatusSection");
|
|
1881
|
+
const list = document.getElementById("serverStatusList");
|
|
1882
|
+
const overallEl = document.getElementById("serverStatusOverall");
|
|
1883
|
+
const blockersEl = document.getElementById("serverStatusBlockers");
|
|
1884
|
+
if (!section || !list || !overallEl || !blockersEl) return;
|
|
1885
|
+
|
|
1886
|
+
const overall = report?.overall || "blocked";
|
|
1887
|
+
section.classList.remove(
|
|
1888
|
+
"server-status-card--ok",
|
|
1889
|
+
"server-status-card--degraded",
|
|
1890
|
+
"server-status-card--blocked"
|
|
1891
|
+
);
|
|
1892
|
+
section.classList.add(`server-status-card--${overall}`);
|
|
1893
|
+
overallEl.textContent = overallStatusCopy(overall);
|
|
1894
|
+
|
|
1895
|
+
list.innerHTML = "";
|
|
1896
|
+
const connectors = Array.isArray(report?.connectors) ? report.connectors : [];
|
|
1897
|
+
for (const connector of connectors) {
|
|
1898
|
+
const li = document.createElement("li");
|
|
1899
|
+
li.className = "server-status-row";
|
|
1900
|
+
|
|
1901
|
+
const main = document.createElement("div");
|
|
1902
|
+
main.className = "server-status-row-main";
|
|
1903
|
+
|
|
1904
|
+
const label = document.createElement("span");
|
|
1905
|
+
label.className = "server-status-label";
|
|
1906
|
+
label.textContent = connector.label || connector.id || "Connector";
|
|
1907
|
+
main.appendChild(label);
|
|
1908
|
+
|
|
1909
|
+
if (connector.provider) {
|
|
1910
|
+
const provider = document.createElement("span");
|
|
1911
|
+
provider.className = "server-status-provider";
|
|
1912
|
+
provider.textContent = connector.provider;
|
|
1913
|
+
main.appendChild(provider);
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
const badge = document.createElement("span");
|
|
1917
|
+
const state = connector.state || "error";
|
|
1918
|
+
badge.className = `server-status-badge server-status-badge--${state}`;
|
|
1919
|
+
badge.textContent = connectorStateLabel(state);
|
|
1920
|
+
|
|
1921
|
+
const detail = document.createElement("p");
|
|
1922
|
+
detail.className = "server-status-detail";
|
|
1923
|
+
detail.textContent = connector.detail || "";
|
|
1924
|
+
|
|
1925
|
+
li.appendChild(main);
|
|
1926
|
+
li.appendChild(badge);
|
|
1927
|
+
li.appendChild(detail);
|
|
1928
|
+
list.appendChild(li);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
const blockers = Array.isArray(report?.blockers) ? report.blockers : [];
|
|
1932
|
+
blockersEl.innerHTML = "";
|
|
1933
|
+
if (blockers.length === 0) {
|
|
1934
|
+
blockersEl.classList.add("hidden");
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
blockersEl.classList.remove("hidden");
|
|
1939
|
+
for (const blocker of blockers) {
|
|
1940
|
+
const card = document.createElement("div");
|
|
1941
|
+
card.className = "server-status-blocker";
|
|
1942
|
+
|
|
1943
|
+
const title = document.createElement("h3");
|
|
1944
|
+
title.textContent = blocker.title || "Connector issue";
|
|
1945
|
+
|
|
1946
|
+
const message = document.createElement("p");
|
|
1947
|
+
message.textContent = blocker.message || "";
|
|
1948
|
+
|
|
1949
|
+
const fix = document.createElement("p");
|
|
1950
|
+
fix.className = "server-status-blocker-fix";
|
|
1951
|
+
fix.textContent = blocker.fixHint
|
|
1952
|
+
? `How to fix: ${blocker.fixHint}`
|
|
1953
|
+
: "How to fix: update Secrets / env and restart the CMS.";
|
|
1954
|
+
|
|
1955
|
+
card.appendChild(title);
|
|
1956
|
+
card.appendChild(message);
|
|
1957
|
+
card.appendChild(fix);
|
|
1958
|
+
blockersEl.appendChild(card);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
async function fetchServerStatus() {
|
|
1963
|
+
try {
|
|
1964
|
+
const res = await fetch("/status", { cache: "no-store" });
|
|
1965
|
+
const data = await res.json();
|
|
1966
|
+
if (!res.ok) {
|
|
1967
|
+
renderServerStatus({
|
|
1968
|
+
overall: "blocked",
|
|
1969
|
+
connectors: [
|
|
1970
|
+
{
|
|
1971
|
+
id: "server",
|
|
1972
|
+
label: "Server",
|
|
1973
|
+
state: "error",
|
|
1974
|
+
detail: data?.error || `HTTP ${res.status}`
|
|
1975
|
+
}
|
|
1976
|
+
],
|
|
1977
|
+
blockers: [
|
|
1978
|
+
{
|
|
1979
|
+
connectorId: "server",
|
|
1980
|
+
title: "Could not load server status",
|
|
1981
|
+
message: data?.error || `HTTP ${res.status}`,
|
|
1982
|
+
fixHint: "Confirm the CMS is running with @tomorrowos/sdk that includes GET /status."
|
|
1983
|
+
}
|
|
1984
|
+
]
|
|
1985
|
+
});
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
renderServerStatus(data);
|
|
1989
|
+
} catch (err) {
|
|
1990
|
+
renderServerStatus({
|
|
1991
|
+
overall: "blocked",
|
|
1992
|
+
connectors: [
|
|
1993
|
+
{
|
|
1994
|
+
id: "server",
|
|
1995
|
+
label: "Server",
|
|
1996
|
+
state: "error",
|
|
1997
|
+
detail: err?.message || "Network error"
|
|
1998
|
+
}
|
|
1999
|
+
],
|
|
2000
|
+
blockers: [
|
|
2001
|
+
{
|
|
2002
|
+
connectorId: "server",
|
|
2003
|
+
title: "CMS unreachable",
|
|
2004
|
+
message: err?.message || "Network error",
|
|
2005
|
+
fixHint: "Start the CMS (`npm run start`) and reload this page."
|
|
2006
|
+
}
|
|
2007
|
+
]
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
|
|
1860
2012
|
function startDevicePolling() {
|
|
1861
2013
|
if (devicePollTimer) clearInterval(devicePollTimer);
|
|
1862
2014
|
void fetchDevices();
|
|
1863
2015
|
devicePollTimer = setInterval(() => void fetchDevices(), 8000);
|
|
1864
2016
|
}
|
|
1865
2017
|
|
|
2018
|
+
function startServerStatusPolling() {
|
|
2019
|
+
if (serverStatusTimer) clearInterval(serverStatusTimer);
|
|
2020
|
+
void fetchServerStatus();
|
|
2021
|
+
serverStatusTimer = setInterval(() => void fetchServerStatus(), 30000);
|
|
2022
|
+
}
|
|
2023
|
+
|
|
1866
2024
|
document.addEventListener("DOMContentLoaded", () => {
|
|
1867
2025
|
const cmsUrlSection = document.getElementById("cmsUrlSection");
|
|
1868
2026
|
if (cmsUrlSection && !isLocalPanelHost(window.location.hostname)) {
|
|
@@ -1876,6 +2034,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
1876
2034
|
updatePlaylistEditorVisibility();
|
|
1877
2035
|
void fetchPlaylists();
|
|
1878
2036
|
startDevicePolling();
|
|
2037
|
+
startServerStatusPolling();
|
|
2038
|
+
|
|
2039
|
+
document
|
|
2040
|
+
.getElementById("serverStatusRefreshBtn")
|
|
2041
|
+
?.addEventListener("click", () => void fetchServerStatus());
|
|
1879
2042
|
|
|
1880
2043
|
document.getElementById("newPlaylistBtn")?.addEventListener("click", newPlaylistDraft);
|
|
1881
2044
|
document.getElementById("savePlaylistBtn")?.addEventListener("click", () => void saveCurrentPlaylist());
|
|
@@ -118,6 +118,153 @@ body {
|
|
|
118
118
|
color: #666;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
.server-status-card {
|
|
122
|
+
border-color: #e4e1dc;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.server-status-header {
|
|
126
|
+
display: flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
justify-content: space-between;
|
|
129
|
+
gap: 0.75rem;
|
|
130
|
+
margin-bottom: 0.35rem;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.server-status-header h2 {
|
|
134
|
+
margin: 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.server-status-header button {
|
|
138
|
+
font-size: 0.78rem;
|
|
139
|
+
padding: 0.35rem 0.65rem;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.server-status-overall {
|
|
143
|
+
margin-bottom: 0.65rem;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.server-status-list {
|
|
147
|
+
list-style: none;
|
|
148
|
+
margin: 0;
|
|
149
|
+
padding: 0;
|
|
150
|
+
display: grid;
|
|
151
|
+
gap: 0.45rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.server-status-row {
|
|
155
|
+
display: grid;
|
|
156
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
157
|
+
gap: 0.35rem 0.75rem;
|
|
158
|
+
align-items: baseline;
|
|
159
|
+
padding: 0.55rem 0.65rem;
|
|
160
|
+
border: 1px solid #ece8e2;
|
|
161
|
+
border-radius: 8px;
|
|
162
|
+
background: #fafaf9;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.server-status-row-main {
|
|
166
|
+
display: flex;
|
|
167
|
+
flex-wrap: wrap;
|
|
168
|
+
align-items: baseline;
|
|
169
|
+
gap: 0.35rem 0.55rem;
|
|
170
|
+
min-width: 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.server-status-label {
|
|
174
|
+
font-size: 0.85rem;
|
|
175
|
+
font-weight: 600;
|
|
176
|
+
color: #0a0908;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.server-status-provider {
|
|
180
|
+
font-size: 0.72rem;
|
|
181
|
+
color: #888;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.server-status-detail {
|
|
185
|
+
grid-column: 1 / -1;
|
|
186
|
+
margin: 0;
|
|
187
|
+
font-size: 0.75rem;
|
|
188
|
+
color: #555;
|
|
189
|
+
line-height: 1.4;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.server-status-badge {
|
|
193
|
+
font-size: 0.72rem;
|
|
194
|
+
font-weight: 600;
|
|
195
|
+
letter-spacing: 0.02em;
|
|
196
|
+
text-transform: uppercase;
|
|
197
|
+
padding: 0.18rem 0.45rem;
|
|
198
|
+
border-radius: 999px;
|
|
199
|
+
border: 1px solid transparent;
|
|
200
|
+
white-space: nowrap;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.server-status-badge--ok {
|
|
204
|
+
color: #067647;
|
|
205
|
+
background: #ecfdf3;
|
|
206
|
+
border-color: #abefc6;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.server-status-badge--warn {
|
|
210
|
+
color: #b54708;
|
|
211
|
+
background: #fffaeb;
|
|
212
|
+
border-color: #fedf89;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.server-status-badge--error,
|
|
216
|
+
.server-status-badge--missing {
|
|
217
|
+
color: #b42318;
|
|
218
|
+
background: #fff5f5;
|
|
219
|
+
border-color: #fecdca;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.server-status-blockers {
|
|
223
|
+
margin-top: 0.75rem;
|
|
224
|
+
display: grid;
|
|
225
|
+
gap: 0.55rem;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.server-status-blocker {
|
|
229
|
+
padding: 0.7rem 0.8rem;
|
|
230
|
+
border-radius: 8px;
|
|
231
|
+
border: 1px solid #fecdca;
|
|
232
|
+
background: #fff8f7;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.server-status-blocker h3 {
|
|
236
|
+
margin: 0 0 0.3rem;
|
|
237
|
+
font-size: 0.85rem;
|
|
238
|
+
color: #b42318;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.server-status-blocker p {
|
|
242
|
+
margin: 0;
|
|
243
|
+
font-size: 0.78rem;
|
|
244
|
+
color: #444;
|
|
245
|
+
line-height: 1.45;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.server-status-blocker p + p {
|
|
249
|
+
margin-top: 0.35rem;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.server-status-blocker-fix {
|
|
253
|
+
color: #0a0908;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.server-status-card--ok {
|
|
257
|
+
border-color: #abefc6;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.server-status-card--degraded {
|
|
261
|
+
border-color: #fedf89;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.server-status-card--blocked {
|
|
265
|
+
border-color: #fecdca;
|
|
266
|
+
}
|
|
267
|
+
|
|
121
268
|
.devices-grid {
|
|
122
269
|
display: grid;
|
|
123
270
|
grid-template-columns: repeat(auto-fill, minmax(308px, 1fr));
|