@tensorix/metahub 0.5.1 → 0.5.2
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/cli.js +513 -133
- package/dist/cli.js.map +20 -19
- package/dist/core/audit.d.ts.map +1 -1
- package/dist/core/blobs-core.d.ts.map +1 -1
- package/dist/core/crdt.d.ts.map +1 -1
- package/dist/core/db.d.ts +1 -1
- package/dist/core/db.d.ts.map +1 -1
- package/dist/core/integrity.d.ts +5 -0
- package/dist/core/integrity.d.ts.map +1 -1
- package/dist/core/node.d.ts +52 -6
- package/dist/core/node.d.ts.map +1 -1
- package/dist/core/schema-init.d.ts +3 -0
- package/dist/core/schema-init.d.ts.map +1 -1
- package/dist/core/schema.d.ts +1 -1
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/snapshot.d.ts.map +1 -1
- package/dist/core/sync/devices.d.ts +6 -0
- package/dist/core/sync/devices.d.ts.map +1 -1
- package/dist/core/sync/peers-routes.d.ts.map +1 -1
- package/dist/core/sync/peers.d.ts +2 -0
- package/dist/core/sync/peers.d.ts.map +1 -1
- package/dist/core/sync/server.d.ts.map +1 -1
- package/dist/core/sync/storage.d.ts +4 -0
- package/dist/core/sync/storage.d.ts.map +1 -1
- package/dist/core/tables.d.ts +16 -0
- package/dist/core/tables.d.ts.map +1 -0
- package/dist/db-worker.js +72 -56
- package/dist/edge-worker.js +35 -7
- package/dist/index.js +468 -101
- package/dist/index.js.map +17 -16
- package/dist/metahub-sdk.js +5 -5
- package/dist/sw.js +5 -5
- package/dist/webui/server/routes.d.ts.map +1 -1
- package/dist/webui.js +124 -123
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -352,6 +352,22 @@ CREATE TABLE IF NOT EXISTS blob_policy (
|
|
|
352
352
|
redundancy TEXT, -- 'all' | 'any'
|
|
353
353
|
__deleted INTEGER NOT NULL DEFAULT 0
|
|
354
354
|
);
|
|
355
|
+
|
|
356
|
+
-- Synced device roster (system tier, see tables.ts): one row per node id, written
|
|
357
|
+
-- by the device itself on open (describeSelf) and by anyone who renames it.
|
|
358
|
+
-- Replicates so every device shows the same name and icon for a peer \u2014 the only
|
|
359
|
+
-- record a purely bucket-joined device leaves besides its oplog stream. Each
|
|
360
|
+
-- column is its own LWW register, so a rename and a platform refresh never
|
|
361
|
+
-- clobber each other. meta.node_label survives as a node-local read fallback.
|
|
362
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
363
|
+
id TEXT PRIMARY KEY, -- node_id
|
|
364
|
+
label TEXT, -- shared display name; any device may set it
|
|
365
|
+
platform TEXT, -- macos | windows | linux | ios | android | web
|
|
366
|
+
form TEXT, -- laptop | desktop | phone | server | browser
|
|
367
|
+
app TEXT, -- cli | server | desktop | web
|
|
368
|
+
first_seen INTEGER, -- epoch ms of the device's first self-description
|
|
369
|
+
__deleted INTEGER NOT NULL DEFAULT 0
|
|
370
|
+
);
|
|
355
371
|
`, FTS_SCHEMA = `
|
|
356
372
|
CREATE VIRTUAL TABLE IF NOT EXISTS search_fts USING fts5(
|
|
357
373
|
kind UNINDEXED,
|
|
@@ -410,27 +426,133 @@ function getNodeId(db) {
|
|
|
410
426
|
db.query("INSERT INTO meta (key, value) VALUES ('node_id', ?)").run(id);
|
|
411
427
|
return id;
|
|
412
428
|
}
|
|
413
|
-
function
|
|
429
|
+
function readNodeMeta(db, nodeId) {
|
|
430
|
+
const r = db.query(`SELECT ${NODE_COLS} FROM nodes WHERE id = ? AND __deleted = 0`).get(nodeId);
|
|
431
|
+
return r ? rowToMeta(r) : null;
|
|
432
|
+
}
|
|
433
|
+
function listNodeMeta(db) {
|
|
434
|
+
return db.query(`SELECT ${NODE_COLS} FROM nodes WHERE __deleted = 0`).all().map(rowToMeta);
|
|
435
|
+
}
|
|
436
|
+
function legacySelfLabel(db) {
|
|
414
437
|
const row = db.query("SELECT value FROM meta WHERE key = 'node_label'").get();
|
|
415
438
|
return row?.value ?? null;
|
|
416
439
|
}
|
|
417
|
-
function
|
|
418
|
-
if (label == null
|
|
440
|
+
function mirrorLegacySelfLabel(db, label) {
|
|
441
|
+
if (label == null) {
|
|
419
442
|
db.query("DELETE FROM meta WHERE key = 'node_label'").run();
|
|
420
443
|
return;
|
|
421
444
|
}
|
|
422
|
-
db.query("INSERT INTO meta (key, value) VALUES ('node_label', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(label
|
|
445
|
+
db.query("INSERT INTO meta (key, value) VALUES ('node_label', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(label);
|
|
446
|
+
}
|
|
447
|
+
function nodeLabelOf(db, nodeId) {
|
|
448
|
+
const synced = readNodeMeta(db, nodeId)?.label;
|
|
449
|
+
if (synced)
|
|
450
|
+
return synced;
|
|
451
|
+
const peer = db.query("SELECT label FROM peers WHERE node_id = ? AND label IS NOT NULL AND label <> '' LIMIT 1").get(nodeId);
|
|
452
|
+
if (peer?.label)
|
|
453
|
+
return peer.label;
|
|
454
|
+
return nodeId === getNodeId(db) ? legacySelfLabel(db) : null;
|
|
455
|
+
}
|
|
456
|
+
function getNodeLabel(db) {
|
|
457
|
+
return nodeLabelOf(db, getNodeId(db));
|
|
458
|
+
}
|
|
459
|
+
function setNodeLabel(db, label, nodeId) {
|
|
460
|
+
const id = nodeId ?? getNodeId(db);
|
|
461
|
+
const next = label == null || label.trim() === "" ? null : label.trim();
|
|
462
|
+
const cur = readNodeMeta(db, id);
|
|
463
|
+
if (id === getNodeId(db))
|
|
464
|
+
mirrorLegacySelfLabel(db, next);
|
|
465
|
+
if (cur && cur.label === next)
|
|
466
|
+
return;
|
|
467
|
+
if (!cur && next == null)
|
|
468
|
+
return;
|
|
469
|
+
withChangeGroup("node", () => {
|
|
470
|
+
emit(db, "nodes", id, "label", next);
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
function describeSelf(db, info = {}) {
|
|
474
|
+
const id = getNodeId(db);
|
|
475
|
+
const cur = readNodeMeta(db, id);
|
|
476
|
+
const fields = {};
|
|
477
|
+
if (info.platform !== undefined && (cur?.platform ?? null) !== info.platform)
|
|
478
|
+
fields.platform = info.platform;
|
|
479
|
+
if (info.form !== undefined && (cur?.form ?? null) !== info.form)
|
|
480
|
+
fields.form = info.form;
|
|
481
|
+
if (info.app !== undefined && (cur?.app ?? null) !== info.app)
|
|
482
|
+
fields.app = info.app;
|
|
483
|
+
if (cur?.first_seen == null)
|
|
484
|
+
fields.first_seen = info.now ?? Date.now();
|
|
485
|
+
if (!cur?.label) {
|
|
486
|
+
const seed = legacySelfLabel(db) ?? info.defaultLabel ?? null;
|
|
487
|
+
if (seed)
|
|
488
|
+
fields.label = seed;
|
|
489
|
+
}
|
|
490
|
+
if (Object.keys(fields).length > 0) {
|
|
491
|
+
withChangeGroup("node", () => {
|
|
492
|
+
for (const [col, v] of Object.entries(fields))
|
|
493
|
+
emit(db, "nodes", id, col, v);
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
return readNodeMeta(db, id);
|
|
497
|
+
}
|
|
498
|
+
function platformFromProcess(p) {
|
|
499
|
+
switch (p) {
|
|
500
|
+
case "darwin":
|
|
501
|
+
return "macos";
|
|
502
|
+
case "win32":
|
|
503
|
+
return "windows";
|
|
504
|
+
case "linux":
|
|
505
|
+
return "linux";
|
|
506
|
+
case "android":
|
|
507
|
+
return "android";
|
|
508
|
+
default:
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function defaultForm(platform, app) {
|
|
513
|
+
if (app === "web")
|
|
514
|
+
return "browser";
|
|
515
|
+
if (platform === "ios" || platform === "android")
|
|
516
|
+
return "phone";
|
|
517
|
+
if (platform === "linux")
|
|
518
|
+
return "server";
|
|
519
|
+
if (platform === "macos" || platform === "windows")
|
|
520
|
+
return "laptop";
|
|
521
|
+
return null;
|
|
423
522
|
}
|
|
424
523
|
function displayNodes(db) {
|
|
425
524
|
const self = getNodeId(db);
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
525
|
+
const ids = new Set([self]);
|
|
526
|
+
for (const r of listNodeMeta(db))
|
|
527
|
+
ids.add(r.node_id);
|
|
528
|
+
const peers = db.query("SELECT node_id FROM peers WHERE node_id IS NOT NULL AND node_id <> '' GROUP BY node_id").all();
|
|
529
|
+
for (const p of peers)
|
|
530
|
+
ids.add(p.node_id);
|
|
531
|
+
const out = [...ids].map((node_id) => {
|
|
532
|
+
const m = readNodeMeta(db, node_id);
|
|
533
|
+
return {
|
|
534
|
+
node_id,
|
|
535
|
+
label: nodeLabelOf(db, node_id),
|
|
536
|
+
self: node_id === self,
|
|
537
|
+
platform: m?.platform ?? null,
|
|
538
|
+
form: m?.form ?? null,
|
|
539
|
+
app: m?.app ?? null
|
|
540
|
+
};
|
|
541
|
+
});
|
|
542
|
+
out.sort((a, b) => Number(b.self) - Number(a.self));
|
|
543
|
+
return out;
|
|
431
544
|
}
|
|
545
|
+
var NODE_COLS = "id, label, platform, form, app, first_seen", rowToMeta = (r) => ({
|
|
546
|
+
node_id: r.id,
|
|
547
|
+
label: r.label,
|
|
548
|
+
platform: r.platform ?? null,
|
|
549
|
+
form: r.form ?? null,
|
|
550
|
+
app: r.app ?? null,
|
|
551
|
+
first_seen: r.first_seen
|
|
552
|
+
});
|
|
432
553
|
var init_node = __esm(() => {
|
|
433
554
|
init_ids();
|
|
555
|
+
init_crdt();
|
|
434
556
|
});
|
|
435
557
|
|
|
436
558
|
// src/core/hlc.ts
|
|
@@ -870,6 +992,10 @@ var init_crdt = __esm(() => {
|
|
|
870
992
|
blob_policy: {
|
|
871
993
|
table: "blob_policy",
|
|
872
994
|
cols: new Set(["full_nodes", "redundancy", "__deleted"])
|
|
995
|
+
},
|
|
996
|
+
nodes: {
|
|
997
|
+
table: "nodes",
|
|
998
|
+
cols: new Set(["label", "platform", "form", "app", "first_seen", "__deleted"])
|
|
873
999
|
}
|
|
874
1000
|
};
|
|
875
1001
|
RECORD_META = new Set(["database_id", "created_hlc", "order_key", "__deleted"]);
|
|
@@ -887,7 +1013,8 @@ var init_crdt = __esm(() => {
|
|
|
887
1013
|
"sites:spa",
|
|
888
1014
|
"site_channels:__deleted",
|
|
889
1015
|
"site_files:__deleted",
|
|
890
|
-
"blob_policy:__deleted"
|
|
1016
|
+
"blob_policy:__deleted",
|
|
1017
|
+
"nodes:__deleted"
|
|
891
1018
|
]);
|
|
892
1019
|
});
|
|
893
1020
|
|
|
@@ -2205,17 +2332,14 @@ function cacheStats(db) {
|
|
|
2205
2332
|
}
|
|
2206
2333
|
function knownNodes(db) {
|
|
2207
2334
|
const self = getNodeId(db);
|
|
2208
|
-
const
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
for (const p of peers)
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
if (!byId.has(g.node_id))
|
|
2217
|
-
byId.set(g.node_id, null);
|
|
2218
|
-
return [...byId.entries()].map(([nodeId, label]) => ({ nodeId, label, self: nodeId === self }));
|
|
2335
|
+
const ids = new Set([self]);
|
|
2336
|
+
for (const r of db.query("SELECT id FROM nodes WHERE __deleted = 0").all())
|
|
2337
|
+
ids.add(r.id);
|
|
2338
|
+
for (const p of db.query("SELECT DISTINCT node_id FROM peers WHERE node_id IS NOT NULL").all())
|
|
2339
|
+
ids.add(p.node_id);
|
|
2340
|
+
for (const g of db.query("SELECT DISTINCT node_id FROM peer_grants WHERE node_id IS NOT NULL").all())
|
|
2341
|
+
ids.add(g.node_id);
|
|
2342
|
+
return [...ids].map((nodeId) => ({ nodeId, label: nodeLabelOf(db, nodeId), self: nodeId === self }));
|
|
2219
2343
|
}
|
|
2220
2344
|
var POLICY_ID = "default", blobBytesResolver = null, setFullNodes = (db, nodeIds) => {
|
|
2221
2345
|
withChangeGroup(null, () => {
|
|
@@ -2468,21 +2592,28 @@ function migrateSitesAccess(db) {
|
|
|
2468
2592
|
backfill("public_grants", "NULL");
|
|
2469
2593
|
}
|
|
2470
2594
|
}
|
|
2471
|
-
function
|
|
2472
|
-
if (!tableExists3(db,
|
|
2595
|
+
function replayDataset(db, dataset) {
|
|
2596
|
+
if (!tableExists3(db, dataset))
|
|
2473
2597
|
return;
|
|
2474
|
-
const
|
|
2598
|
+
const key = `${dataset}_replay_seq`;
|
|
2599
|
+
const wm = db.query("SELECT value FROM meta WHERE key = ?").get(key);
|
|
2475
2600
|
const since = wm ? Number(wm.value) : 0;
|
|
2476
2601
|
const changes = db.query(`SELECT seq, ${CHANGE_SELECT} FROM crdt_changes
|
|
2477
|
-
WHERE dataset =
|
|
2602
|
+
WHERE dataset = ? AND seq > ? ORDER BY seq`).all(dataset, since);
|
|
2478
2603
|
if (changes.length === 0)
|
|
2479
2604
|
return;
|
|
2480
2605
|
db.transaction((rows) => {
|
|
2481
2606
|
for (const change of rows)
|
|
2482
2607
|
applyChange(db, change);
|
|
2483
|
-
db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(
|
|
2608
|
+
db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, String(rows[rows.length - 1].seq));
|
|
2484
2609
|
})(changes);
|
|
2485
2610
|
}
|
|
2611
|
+
function migrateSiteChannels(db) {
|
|
2612
|
+
replayDataset(db, "site_channels");
|
|
2613
|
+
}
|
|
2614
|
+
function migrateNodes(db) {
|
|
2615
|
+
replayDataset(db, "nodes");
|
|
2616
|
+
}
|
|
2486
2617
|
function initSchema(db) {
|
|
2487
2618
|
runSchema(db);
|
|
2488
2619
|
migrateOplog(db);
|
|
@@ -2497,8 +2628,8 @@ function initSchema(db) {
|
|
|
2497
2628
|
migrateShares(db);
|
|
2498
2629
|
migrateSitesAccess(db);
|
|
2499
2630
|
migrateSiteChannels(db);
|
|
2631
|
+
migrateNodes(db);
|
|
2500
2632
|
}
|
|
2501
|
-
var SITE_CHANNELS_REPLAY_KEY = "site_channels_replay_seq";
|
|
2502
2633
|
var init_schema_init = __esm(() => {
|
|
2503
2634
|
init_records();
|
|
2504
2635
|
init_documents();
|
|
@@ -9025,7 +9156,7 @@ var package_default;
|
|
|
9025
9156
|
var init_package = __esm(() => {
|
|
9026
9157
|
package_default = {
|
|
9027
9158
|
name: "@tensorix/metahub",
|
|
9028
|
-
version: "0.5.
|
|
9159
|
+
version: "0.5.2",
|
|
9029
9160
|
description: "Metahub core library and CLI",
|
|
9030
9161
|
license: "AGPL-3.0-only",
|
|
9031
9162
|
type: "module",
|
|
@@ -10527,6 +10658,8 @@ async function addAndSyncStoragePeer(db, spec) {
|
|
|
10527
10658
|
publish: spec.publish,
|
|
10528
10659
|
priority: spec.priority
|
|
10529
10660
|
};
|
|
10661
|
+
if (spec.provider)
|
|
10662
|
+
config.provider = spec.provider;
|
|
10530
10663
|
if (encrypt) {
|
|
10531
10664
|
if (spec.recoveryCode) {
|
|
10532
10665
|
const rawKey = await decodeRecoveryCode(spec.recoveryCode);
|
|
@@ -12163,10 +12296,14 @@ function listDevices(db) {
|
|
|
12163
12296
|
const ensure = (nodeId) => {
|
|
12164
12297
|
let v = byNode.get(nodeId);
|
|
12165
12298
|
if (!v) {
|
|
12299
|
+
const meta = readNodeMeta(db, nodeId);
|
|
12166
12300
|
v = {
|
|
12167
12301
|
nodeId,
|
|
12168
|
-
label:
|
|
12302
|
+
label: nodeLabelOf(db, nodeId),
|
|
12169
12303
|
self: nodeId === self,
|
|
12304
|
+
platform: meta?.platform ?? null,
|
|
12305
|
+
form: meta?.form ?? null,
|
|
12306
|
+
app: meta?.app ?? null,
|
|
12170
12307
|
channels: [],
|
|
12171
12308
|
lastActivityAt: null,
|
|
12172
12309
|
revocable: "none",
|
|
@@ -12177,7 +12314,9 @@ function listDevices(db) {
|
|
|
12177
12314
|
}
|
|
12178
12315
|
return v;
|
|
12179
12316
|
};
|
|
12180
|
-
ensure(self)
|
|
12317
|
+
ensure(self);
|
|
12318
|
+
for (const r of db.query("SELECT id FROM nodes WHERE __deleted = 0").all())
|
|
12319
|
+
ensure(r.id);
|
|
12181
12320
|
const oplog = db.query("SELECT node_id, MAX(hlc) AS h FROM crdt_changes GROUP BY node_id").all();
|
|
12182
12321
|
for (const r of oplog)
|
|
12183
12322
|
ensure(r.node_id).channels.push({ kind: "oplog", ref: "", lastSeenAt: parseHlc(r.h).millis });
|
|
@@ -12186,8 +12325,6 @@ function listDevices(db) {
|
|
|
12186
12325
|
if (!p.node_id)
|
|
12187
12326
|
continue;
|
|
12188
12327
|
const v = ensure(p.node_id);
|
|
12189
|
-
if (!v.label)
|
|
12190
|
-
v.label = p.label;
|
|
12191
12328
|
v.channels.push({
|
|
12192
12329
|
kind: "paired_out",
|
|
12193
12330
|
ref: p.url,
|
|
@@ -12205,6 +12342,9 @@ function listDevices(db) {
|
|
|
12205
12342
|
nodeId: null,
|
|
12206
12343
|
label: null,
|
|
12207
12344
|
self: false,
|
|
12345
|
+
platform: null,
|
|
12346
|
+
form: null,
|
|
12347
|
+
app: null,
|
|
12208
12348
|
channels: [ch],
|
|
12209
12349
|
lastActivityAt: g.created_at,
|
|
12210
12350
|
revocable: "yes",
|
|
@@ -12324,7 +12464,8 @@ function s3PeerViews(db) {
|
|
|
12324
12464
|
prefix: c.prefix ?? null,
|
|
12325
12465
|
accessKeyId: c.accessKeyId ?? null,
|
|
12326
12466
|
encrypt: c.encrypt !== false,
|
|
12327
|
-
virtualHostedStyle: c.virtualHostedStyle ?? null
|
|
12467
|
+
virtualHostedStyle: c.virtualHostedStyle ?? null,
|
|
12468
|
+
provider: c.provider ?? null
|
|
12328
12469
|
};
|
|
12329
12470
|
});
|
|
12330
12471
|
}
|
|
@@ -12401,7 +12542,8 @@ var init_peers_routes = __esm(() => {
|
|
|
12401
12542
|
prefix: string2().optional(),
|
|
12402
12543
|
encrypt: boolean2().optional(),
|
|
12403
12544
|
passphrase: string2().optional(),
|
|
12404
|
-
corsOrigins: array(string2()).optional()
|
|
12545
|
+
corsOrigins: array(string2()).optional(),
|
|
12546
|
+
provider: string2().optional()
|
|
12405
12547
|
});
|
|
12406
12548
|
S3PeerSchema = object({
|
|
12407
12549
|
url: string2(),
|
|
@@ -12418,7 +12560,8 @@ var init_peers_routes = __esm(() => {
|
|
|
12418
12560
|
prefix: string2().nullable(),
|
|
12419
12561
|
accessKeyId: string2().nullable(),
|
|
12420
12562
|
encrypt: boolean2(),
|
|
12421
|
-
virtualHostedStyle: boolean2().nullable()
|
|
12563
|
+
virtualHostedStyle: boolean2().nullable(),
|
|
12564
|
+
provider: string2().nullable()
|
|
12422
12565
|
});
|
|
12423
12566
|
DataPlaceSchema = object({
|
|
12424
12567
|
kind: _enum(["self", "device", "bucket"]),
|
|
@@ -15373,20 +15516,42 @@ init_paths();
|
|
|
15373
15516
|
// src/core/db.ts
|
|
15374
15517
|
init_paths();
|
|
15375
15518
|
init_schema_init();
|
|
15519
|
+
init_node();
|
|
15376
15520
|
init_schema_init();
|
|
15377
15521
|
import { Database } from "bun:sqlite";
|
|
15378
15522
|
import { mkdirSync } from "fs";
|
|
15523
|
+
import { hostname } from "os";
|
|
15379
15524
|
function ensureDirs() {
|
|
15380
15525
|
mkdirSync(metahubHome(), { recursive: true });
|
|
15381
15526
|
mkdirSync(cacheDir(), { recursive: true });
|
|
15382
15527
|
}
|
|
15528
|
+
function appKind() {
|
|
15529
|
+
const v = process.env.METAHUB_APP;
|
|
15530
|
+
return v === "server" || v === "desktop" || v === "web" ? v : "cli";
|
|
15531
|
+
}
|
|
15383
15532
|
function openMetahub() {
|
|
15384
15533
|
ensureDirs();
|
|
15385
15534
|
const db = new Database(dbPath(), { create: true });
|
|
15386
15535
|
db.exec("PRAGMA journal_mode = WAL;");
|
|
15387
15536
|
initSchema(db);
|
|
15537
|
+
const app = appKind();
|
|
15538
|
+
const platform = platformFromProcess(process.platform);
|
|
15539
|
+
describeSelf(db, {
|
|
15540
|
+
app,
|
|
15541
|
+
platform,
|
|
15542
|
+
form: defaultForm(platform, app),
|
|
15543
|
+
defaultLabel: safeHostname()
|
|
15544
|
+
});
|
|
15388
15545
|
return db;
|
|
15389
15546
|
}
|
|
15547
|
+
function safeHostname() {
|
|
15548
|
+
try {
|
|
15549
|
+
const h = hostname();
|
|
15550
|
+
return h ? h.replace(/\.local$/i, "") : null;
|
|
15551
|
+
} catch {
|
|
15552
|
+
return null;
|
|
15553
|
+
}
|
|
15554
|
+
}
|
|
15390
15555
|
|
|
15391
15556
|
// src/core/index.ts
|
|
15392
15557
|
init_ids();
|
|
@@ -15677,6 +15842,7 @@ function scalarHtml(specUrl) {
|
|
|
15677
15842
|
}
|
|
15678
15843
|
function startServer(opts = {}) {
|
|
15679
15844
|
registerRoomBlobResolver(resolveBlob);
|
|
15845
|
+
process.env.METAHUB_APP ??= "server";
|
|
15680
15846
|
const tDb = Date.now();
|
|
15681
15847
|
const db = openMetahub();
|
|
15682
15848
|
const dbMs = Date.now() - tDb;
|
|
@@ -16171,9 +16337,34 @@ var styles_default = ` :root {
|
|
|
16171
16337
|
.set-row-line { display:flex; align-items:center; gap:14px; min-height:44px; padding:5px 0; }
|
|
16172
16338
|
.set-row.clickable > .set-row-line { cursor:pointer; }
|
|
16173
16339
|
.set-row-text { flex:1 1 auto; min-width:0; }
|
|
16174
|
-
.set-row-title { font-size:14px; font-weight:550; color:var(--fg); }
|
|
16175
|
-
.set-row-
|
|
16340
|
+
.set-row-title { font-size:14px; font-weight:550; color:var(--fg); display:flex; align-items:center; gap:8px; min-width:0; }
|
|
16341
|
+
.set-row-title > .set-row-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
16342
|
+
.set-row-caption { font-size:12px; color:var(--muted); margin-top:1px; display:flex; align-items:center; gap:6px;
|
|
16176
16343
|
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
16344
|
+
/* leading glyph (device form / bucket) \u2014 a soft tile so rows scan by shape */
|
|
16345
|
+
.set-row-lead { flex:none; width:28px; height:28px; display:grid; place-items:center; color:var(--muted);
|
|
16346
|
+
border-radius:7px; background:var(--surface-2); }
|
|
16347
|
+
.set-row-lead .ico { width:15px; height:15px; }
|
|
16348
|
+
.set-row.dim > .set-row-line { opacity:.62; }
|
|
16349
|
+
/* status dot in a caption: the only colour a row carries */
|
|
16350
|
+
.cap-dot { display:inline-block; flex:none; width:7px; height:7px; border-radius:50%; background:var(--muted); }
|
|
16351
|
+
.cap-dot.ok { background:var(--success); } .cap-dot.warn { background:var(--warning,#9a6700); } .cap-dot.err { background:var(--danger); }
|
|
16352
|
+
.cap-err { color:var(--danger); }
|
|
16353
|
+
/* tag variants next to a title: neutral outline (default), accent (roles), self (soft fill) */
|
|
16354
|
+
.set-row-title .set-row-tag { margin-left:0; vertical-align:0; flex:none; }
|
|
16355
|
+
.set-row-tag.accent { color:var(--accent); background:var(--accent-soft); border-color:transparent; }
|
|
16356
|
+
.set-row-tag.self { color:var(--fg-soft); background:var(--surface-2); border-color:transparent; }
|
|
16357
|
+
.set-row-tag.mono { font-family:var(--mono); letter-spacing:-.01em; }
|
|
16358
|
+
/* quiet "+ add" row closing a list, and a "N more\u2026" fold */
|
|
16359
|
+
.add-row { display:flex; align-items:center; gap:8px; width:100%; min-height:40px; margin-top:2px; padding:0 2px;
|
|
16360
|
+
color:var(--muted); font-size:var(--fs-ui); border-radius:var(--radius-sm); text-align:left; }
|
|
16361
|
+
.add-row:hover { color:var(--fg); background:var(--hover); }
|
|
16362
|
+
.add-row .ico { width:15px; height:15px; }
|
|
16363
|
+
.more-row { display:inline-flex; align-items:center; gap:6px; padding:8px 2px; font-size:var(--fs-sm); color:var(--muted); }
|
|
16364
|
+
.more-row:hover { color:var(--fg); }
|
|
16365
|
+
.set-section-head { display:flex; align-items:baseline; gap:10px; margin:0 0 4px 2px; }
|
|
16366
|
+
.set-section-head .set-section-label { margin:0; }
|
|
16367
|
+
.set-section-count { font-size:12px; color:var(--muted); font-variant-numeric:tabular-nums; }
|
|
16177
16368
|
.set-row-control { flex:none; display:flex; align-items:center; gap:8px; }
|
|
16178
16369
|
.set-row-detail { padding:2px 0 12px; }
|
|
16179
16370
|
.set-row.disabled > .set-row-line { opacity:.55; pointer-events:none; }
|
|
@@ -16238,8 +16429,12 @@ var styles_default = ` :root {
|
|
|
16238
16429
|
.audit-detail { padding:0 4px 10px 86px; }
|
|
16239
16430
|
.audit-detail-ent-name { font-size:var(--fs-sm); font-weight:600; margin-top:6px; }
|
|
16240
16431
|
.audit-blocks { font-size:var(--fs-sm); margin-top:2px; }
|
|
16241
|
-
|
|
16242
|
-
.audit-
|
|
16432
|
+
/* quiet tail: left-aligned text button while more exists; a rule once exhausted */
|
|
16433
|
+
.audit-more { display:inline-flex; align-items:center; gap:6px; margin:14px 0 0 2px; font-size:var(--fs-sm); color:var(--muted); }
|
|
16434
|
+
.audit-more:hover { color:var(--fg); }
|
|
16435
|
+
.audit-more.loading { opacity:.6; pointer-events:none; }
|
|
16436
|
+
.audit-end { display:flex; align-items:center; gap:12px; margin:18px 0 0; color:var(--muted); font-size:var(--fs-xs); }
|
|
16437
|
+
.audit-end::before, .audit-end::after { content:""; flex:1; height:1px; background:var(--line); }
|
|
16243
16438
|
@media (hover:none) { .audit-revert { opacity:1; } }
|
|
16244
16439
|
.set-callout.warn { border-color:color-mix(in srgb,var(--warning,#9a6700) 32%,var(--line)); }
|
|
16245
16440
|
/* two-fact meta line under a block title (who publishes \xB7 where creds live) \u2014
|
|
@@ -16354,28 +16549,31 @@ var styles_default = ` :root {
|
|
|
16354
16549
|
/* concurrent problems under the headline (only when >1, else redundant) */
|
|
16355
16550
|
.sync-health-issues { padding:2px 0 4px; }
|
|
16356
16551
|
.sync-health-issue { font-size:12px; color:var(--muted); padding:2px 0 2px 18px; }
|
|
16357
|
-
|
|
16358
|
-
.sync-health-
|
|
16552
|
+
/* \u67E5\u770B\u5404\u5904: a bordered place list (kind \xB7 name \xB7 freshness \xB7 inline retry) */
|
|
16553
|
+
.sync-health-places { margin-top:6px; border:1px solid var(--line); border-radius:var(--radius); overflow:hidden; }
|
|
16554
|
+
.sync-health-place { display:flex; align-items:center; gap:10px; padding:9px 12px; min-width:0; font-size:var(--fs-sm); }
|
|
16359
16555
|
.sync-health-place + .sync-health-place { border-top:1px solid var(--line); }
|
|
16360
|
-
.sync-health-place-kind { flex:none; font-size:
|
|
16361
|
-
|
|
16362
|
-
.sync-health-place-
|
|
16363
|
-
|
|
16364
|
-
.sync-health-place-cap { flex:1; min-width:0; font-size:12px; color:var(--muted);
|
|
16365
|
-
overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
16556
|
+
.sync-health-place-kind { flex:none; width:64px; font-size:var(--fs-xs); color:var(--muted); }
|
|
16557
|
+
.sync-health-place-label { flex:none; font-weight:550; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:40%; }
|
|
16558
|
+
.sync-health-place-cap { flex:1; min-width:0; color:var(--muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
16559
|
+
.sync-health-place .btn { margin-left:auto; }
|
|
16366
16560
|
.sync-health-role { flex:none; font-size:11px; color:var(--accent); background:var(--accent-soft);
|
|
16367
16561
|
border-radius:999px; padding:2px 7px; }
|
|
16368
16562
|
|
|
16369
|
-
/*
|
|
16370
|
-
|
|
16371
|
-
.device-
|
|
16372
|
-
.device-
|
|
16373
|
-
.device-
|
|
16374
|
-
.device-
|
|
16375
|
-
.device-
|
|
16376
|
-
.device-
|
|
16377
|
-
.device-
|
|
16378
|
-
|
|
16563
|
+
/* device roster (settings/devices-page.tsx): expanded detail = key/value
|
|
16564
|
+
table + one action row + one plain sentence \u2014 no gray callout box. */
|
|
16565
|
+
.device-kv { display:grid; grid-template-columns:max-content 1fr; gap:6px 18px; padding:4px 0 10px 42px; font-size:var(--fs-sm); margin:0; }
|
|
16566
|
+
.device-kv dt { color:var(--muted); margin:0; }
|
|
16567
|
+
.device-kv dd { margin:0; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
16568
|
+
.device-kv .mono { font-family:var(--mono); font-size:var(--fs-xs); letter-spacing:-.01em; }
|
|
16569
|
+
.device-actions { display:flex; align-items:center; gap:4px; padding-left:42px; flex-wrap:wrap; }
|
|
16570
|
+
.device-actions .btn { padding:3px 8px; font-size:12px; }
|
|
16571
|
+
.device-actions .btn.danger { color:var(--danger); }
|
|
16572
|
+
.device-actions .btn.danger:hover { background:var(--danger-soft); color:var(--danger); }
|
|
16573
|
+
.device-note { font-size:12px; color:var(--muted); line-height:1.6; padding:4px 0 6px 42px; max-width:60ch; }
|
|
16574
|
+
.device-note b { color:var(--fg-soft); font-weight:600; }
|
|
16575
|
+
.device-note a { color:var(--accent); text-decoration:none; }
|
|
16576
|
+
.device-note a:hover { text-decoration:underline; }
|
|
16379
16577
|
|
|
16380
16578
|
/* cloud accessories panel (CloudPanel: bucket + edge in one glance) */
|
|
16381
16579
|
.cloud-cta { border:1px solid var(--line); border-radius:10px; padding:14px 16px; margin:4px 0 12px;
|
|
@@ -16450,6 +16648,7 @@ var styles_default = ` :root {
|
|
|
16450
16648
|
.btn-primary { background:var(--accent); color:var(--accent-fg); box-shadow:var(--shadow-sm); }
|
|
16451
16649
|
.btn-primary:hover { filter:brightness(1.07); }
|
|
16452
16650
|
.btn-danger { background:var(--danger); color:#fff; } .btn-danger:hover { filter:brightness(1.05); }
|
|
16651
|
+
.btn.sm { padding:3px 8px; font-size:12px; }
|
|
16453
16652
|
/* Notion-style ambient sync indicator: a quiet ghost control living in the
|
|
16454
16653
|
topbar icon row. State reads through the *text/icon colour* \u2014 no filled pill
|
|
16455
16654
|
or drop shadow \u2014 and only the error state escalates to a visible danger tone.
|
|
@@ -16479,6 +16678,26 @@ var styles_default = ` :root {
|
|
|
16479
16678
|
@keyframes save-pop { 0% { transform:scale(.7); } 100% { transform:scale(1); } }
|
|
16480
16679
|
@keyframes save-shake { 10%,90% { transform:translateX(-1px); } 20%,80% { transform:translateX(1.5px); } 30%,50%,70% { transform:translateX(-2px); } 40%,60% { transform:translateX(2px); } }
|
|
16481
16680
|
@keyframes save-kbd-pulse { 0% { transform:scale(1); } 45% { transform:scale(1.05); } 100% { transform:scale(1); } }
|
|
16681
|
+
/* Ambient replica-sync indicator (sync-indicator.tsx): same ghost-control
|
|
16682
|
+
idiom as .share-save \u2014 zero footprint when idle, colour carries state,
|
|
16683
|
+
only error escalates to danger. Lives in .sb-footer (desktop), .sb-head
|
|
16684
|
+
(mobile home) and the topbar (sidebar collapsed). */
|
|
16685
|
+
.sync-ind { display:inline-flex; align-items:center; gap:6px; height:28px; padding:0 8px; border-radius:var(--radius-sm);
|
|
16686
|
+
font-size:var(--fs-xs); color:var(--fg-soft); white-space:nowrap; font-variant-numeric:tabular-nums; flex:none;
|
|
16687
|
+
animation:save-rise .22s ease both; }
|
|
16688
|
+
.sync-ind:hover { background:var(--hover-2); color:var(--fg); }
|
|
16689
|
+
.sync-ind-ico { display:inline-flex; align-items:center; }
|
|
16690
|
+
.sync-ring { width:12px; height:12px; border-radius:50%; border:1.5px solid var(--line-strong);
|
|
16691
|
+
border-top-color:var(--accent); animation:mh-spin .8s linear infinite; }
|
|
16692
|
+
.sync-ind-done { color:var(--success); }
|
|
16693
|
+
.sync-ind-done .sync-ind-ico { animation:save-pop .24s cubic-bezier(.18,.9,.24,1.22) both; }
|
|
16694
|
+
.sync-ind-error { color:var(--danger); }
|
|
16695
|
+
.sync-ind-error:hover { background:var(--danger-soft); color:var(--danger); }
|
|
16696
|
+
/* footer: push right, version stays flush right after it */
|
|
16697
|
+
.sb-footer .sync-ind { margin-left:auto; }
|
|
16698
|
+
.sb-footer .sync-ind + .sbf-ver { margin-left:8px; }
|
|
16699
|
+
/* head slot is mobile-only (mirrors the .sb-head .iconbtn gating) */
|
|
16700
|
+
.sb-head .sync-ind { display:none; }
|
|
16482
16701
|
|
|
16483
16702
|
/* macOS desktop (hiddenInset frameless): drop the sidebar logo and reserve a
|
|
16484
16703
|
draggable strip so the inset traffic lights never overlap our controls.
|
|
@@ -17168,55 +17387,148 @@ var styles_default = ` :root {
|
|
|
17168
17387
|
.site-empty .et { font-size:15px; color:var(--fg); margin-bottom:5px; font-weight:650; letter-spacing:-.01em; }
|
|
17169
17388
|
.site-empty .ed { font-size:var(--fs-sm); margin-bottom:18px; max-width:42ch; line-height:1.55; }
|
|
17170
17389
|
|
|
17171
|
-
/* \u2500\u2500 \u5206\u4EAB (global ShareView) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
17172
|
-
|
|
17173
|
-
|
|
17390
|
+
/* \u2500\u2500 \u5206\u4EAB (global ShareView) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
17391
|
+
One row = object \xB7 status \xB7 one primary action \xB7 overflow. State is the
|
|
17392
|
+
organizing idea: colour lives only in the status dot and the \u53EF\u7F16\u8F91 word;
|
|
17393
|
+
expiry is a share's normal ending and reads muted, never danger. */
|
|
17394
|
+
.shares-page .db-head { align-items:flex-start; gap:16px; margin-bottom:6px; }
|
|
17395
|
+
.shares-page .db-desc { max-width:56ch; }
|
|
17396
|
+
.shv-toolbar { display:flex; align-items:center; gap:10px; flex-wrap:nowrap; padding:10px 0; margin-bottom:8px; }
|
|
17397
|
+
.shv-tools-right { display:flex; align-items:center; gap:10px; flex:none; margin-left:auto; }
|
|
17174
17398
|
.shv-seg { display:inline-flex; background:var(--surface-2); border:1px solid var(--line); border-radius:var(--radius-sm); padding:2px; }
|
|
17175
|
-
.shv-seg-btn { padding:5px 11px; border-radius:calc(var(--radius-sm) - 2px); font-size:13px; color:var(--fg-soft); }
|
|
17399
|
+
.shv-seg-btn { display:inline-flex; align-items:baseline; padding:5px 11px; border-radius:calc(var(--radius-sm) - 2px); font-size:13px; color:var(--fg-soft); }
|
|
17176
17400
|
.shv-seg-btn.active { background:var(--bg); color:var(--fg); box-shadow:var(--shadow-sm); }
|
|
17177
|
-
.shv-
|
|
17178
|
-
.shv-
|
|
17401
|
+
.shv-seg-btn .n { display:inline-flex; justify-content:flex-end; align-items:center; min-width:2ch; margin-left:5px; font-family:var(--mono); font-size:var(--fs-micro); color:var(--muted); font-variant-numeric:tabular-nums; }
|
|
17402
|
+
.shv-seg-btn .skel-n { width:2ch; height:.85em; border-radius:3px; animation:skel-in .25s ease .12s both; }
|
|
17403
|
+
.shv-seg-btn.active .n { color:var(--fg-soft); }
|
|
17404
|
+
.shv-src { gap:5px; color:var(--fg-soft); }
|
|
17405
|
+
.shv-src.on { color:var(--fg); }
|
|
17406
|
+
.shv-src .ico { width:14px; height:14px; opacity:.7; }
|
|
17407
|
+
.shv-search { display:flex; align-items:center; gap:6px; flex:1 1 220px; min-width:140px; max-width:360px; background:var(--bg); border:1px solid var(--line-strong); border-radius:var(--radius-sm); padding:5px 10px; color:var(--muted); }
|
|
17408
|
+
.shv-search:focus-within { border-color:var(--accent); }
|
|
17409
|
+
.shv-search input { border:0; background:none; outline:none; color:var(--fg); font-size:13px; width:100%; min-width:0; }
|
|
17179
17410
|
|
|
17180
17411
|
.shv-list { display:flex; flex-direction:column; gap:8px; list-style:none; padding:0; margin:0; }
|
|
17181
|
-
.shv-row { display:
|
|
17412
|
+
.shv-row { display:grid; grid-template-columns:38px minmax(0,1fr) auto auto; gap:6px 13px; align-items:center;
|
|
17413
|
+
padding:12px 15px; background:var(--bg); border:1px solid var(--line);
|
|
17182
17414
|
border-radius:var(--radius-lg); transition:border-color .12s, box-shadow .12s; }
|
|
17183
17415
|
.shv-row:hover { border-color:var(--line-strong); box-shadow:var(--shadow-sm); }
|
|
17184
17416
|
.shv-ico { width:38px; height:38px; flex:none; display:grid; place-items:center; border-radius:var(--radius-md, 10px);
|
|
17185
17417
|
background:var(--surface-2); color:var(--fg-soft); }
|
|
17186
17418
|
.shv-ico svg { width:19px; height:19px; }
|
|
17187
17419
|
.shv-ico.k-doc { color:var(--accent); background:var(--accent-soft); }
|
|
17420
|
+
.shv-ico.k-database { color:var(--fg-soft); }
|
|
17188
17421
|
.shv-ico.k-site { color:#1a7f5a; }
|
|
17189
|
-
.shv-main {
|
|
17190
|
-
.shv-
|
|
17191
|
-
|
|
17422
|
+
.shv-main { min-width:0; }
|
|
17423
|
+
.shv-title { display:block; max-width:100%; font-weight:600; color:var(--fg); letter-spacing:-.01em;
|
|
17424
|
+
overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-align:left; }
|
|
17192
17425
|
.shv-title:hover { color:var(--accent); }
|
|
17193
|
-
.shv-
|
|
17194
|
-
|
|
17195
|
-
.shv-
|
|
17196
|
-
.shv-
|
|
17197
|
-
.shv-
|
|
17198
|
-
.shv-
|
|
17199
|
-
|
|
17426
|
+
.shv-sub { display:flex; align-items:center; gap:6px; margin-top:3px; min-width:0; font-size:var(--fs-sm); color:var(--muted);
|
|
17427
|
+
white-space:nowrap; overflow:hidden; }
|
|
17428
|
+
.shv-sub > * { flex:none; }
|
|
17429
|
+
.shv-sub > .shv-link { flex:0 1 auto; }
|
|
17430
|
+
.shv-link-text { min-width:0; overflow:hidden; text-overflow:ellipsis; }
|
|
17431
|
+
.shv-sep { color:var(--line-strong); }
|
|
17432
|
+
/* the link itself: mono, copy on click, copy glyph slides in on hover */
|
|
17433
|
+
.shv-link { display:inline-flex; align-items:center; gap:4px; min-width:0; max-width:46ch; font-family:var(--mono); font-size:var(--fs-xs);
|
|
17434
|
+
color:var(--fg-soft); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; border-radius:4px; }
|
|
17435
|
+
.shv-link .ico { width:12px; height:12px; opacity:0; transition:opacity .12s; flex:none; }
|
|
17436
|
+
.shv-link:not(:disabled):hover { color:var(--accent); }
|
|
17437
|
+
.shv-link:not(:disabled):hover .ico { opacity:1; }
|
|
17438
|
+
.shv-link:disabled { cursor:default; text-decoration:line-through; text-decoration-color:var(--line-strong); }
|
|
17439
|
+
.shv-snap { font-variant-numeric:tabular-nums; }
|
|
17440
|
+
.shv-perm.edit { color:var(--danger); font-weight:500; }
|
|
17441
|
+
.shv-lock { display:inline-flex; color:var(--fg-soft); }
|
|
17442
|
+
.shv-lock .ico { width:12px; height:12px; }
|
|
17443
|
+
/* status column: dot + short text, fixed floor so rows never jitter */
|
|
17444
|
+
.shv-status { display:inline-flex; align-items:center; gap:7px; min-width:9ch; font-size:var(--fs-sm); color:var(--fg-soft);
|
|
17445
|
+
font-variant-numeric:tabular-nums; white-space:nowrap; }
|
|
17446
|
+
.shv-dot { width:8px; height:8px; border-radius:50%; background:var(--muted); flex:none; }
|
|
17447
|
+
.shv-status.tone-ok .shv-dot { background:var(--success); }
|
|
17448
|
+
.shv-status.tone-warn { color:var(--warning,#9a6700); }
|
|
17449
|
+
.shv-status.tone-warn .shv-dot { background:var(--warning,#9a6700); }
|
|
17450
|
+
.shv-status.tone-muted { color:var(--muted); }
|
|
17451
|
+
.shv-status.tone-muted .shv-dot { background:transparent; border:1.5px solid var(--line-strong); }
|
|
17452
|
+
.shv-status .sync-ring { width:10px; height:10px; }
|
|
17200
17453
|
.shv-actions { display:flex; align-items:center; gap:6px; flex:none; }
|
|
17201
|
-
.shv-actions .btn { padding:5px 10px; font-size:12px; }
|
|
17454
|
+
.shv-actions .btn { padding:5px 10px; font-size:12px; min-width:76px; justify-content:center; }
|
|
17455
|
+
.shv-actions .btn.txt-danger { color:var(--danger); }
|
|
17456
|
+
.shv-row.st-expired .shv-title { color:var(--fg-soft); }
|
|
17457
|
+
/* expired group sinks below the live list */
|
|
17458
|
+
.shv-group-head { display:flex; align-items:center; gap:8px; margin:22px 2px 8px; font-size:var(--fs-sm); font-weight:600; color:var(--muted); }
|
|
17459
|
+
.shv-group-head .n { font-family:var(--mono); font-size:var(--fs-micro); font-weight:400; }
|
|
17460
|
+
.shv-group-head .btn { margin-left:auto; padding:4px 10px; font-size:12px; }
|
|
17461
|
+
/* list swap: data arriving over the skeleton fades in rather than hard-cutting */
|
|
17462
|
+
.shv-list { animation:skel-in .18s ease both; }
|
|
17463
|
+
|
|
17464
|
+
/* \u2500\u2500 skeletons (skeleton.tsx, primitives.tsx SetRowSkeleton, shares-view
|
|
17465
|
+
ShareListSkeleton). Rows are the REAL row markup; only content is swapped
|
|
17466
|
+
for sheen blocks. .skel-list fades in after 120ms so an instant load
|
|
17467
|
+
never flashes it; --i offsets each row's sheen into one rolling wave. */
|
|
17468
|
+
.skel-list { animation:skel-in .25s ease .12s both; }
|
|
17469
|
+
.skel-lines { display:flex; flex-direction:column; gap:8px; }
|
|
17470
|
+
.skel-lines .skel-s { width:calc(58% + var(--i, 0) * 9%); max-width:100%; }
|
|
17471
|
+
.page-header-sub .skel-s { display:block; width:22ch; }
|
|
17472
|
+
.set-row.skel { pointer-events:none; }
|
|
17473
|
+
.set-row.skel .set-row-text { display:flex; flex-direction:column; gap:5px; padding:3px 0; }
|
|
17474
|
+
.set-row.skel .skel-t { height:14px; width:calc(30% + var(--i, 0) * 8%); border-radius:5px; }
|
|
17475
|
+
.set-row.skel .skel-s { height:11px; width:calc(44% + var(--i, 0) * 7%); border-radius:4px; }
|
|
17476
|
+
.set-row.skel .set-row-lead.skel-b { border-radius:7px; }
|
|
17477
|
+
.skel-switch { width:38px; height:22px; border-radius:999px; }
|
|
17478
|
+
.skel-chevron { width:14px; height:14px; border-radius:4px; }
|
|
17479
|
+
.skel-menu { width:28px; height:28px; border-radius:var(--radius-sm); }
|
|
17480
|
+
.skel-button { width:76px; height:29px; border-radius:var(--radius-sm); }
|
|
17481
|
+
.audit-item.skel .audit-item-line { cursor:default; pointer-events:none; }
|
|
17482
|
+
.audit-item.skel .skel-when { width:56px; height:12px; border-radius:4px; flex:none; }
|
|
17483
|
+
.audit-item.skel .skel-t { height:14px; width:calc(34% + var(--i, 0) * 9%); border-radius:5px; }
|
|
17484
|
+
.site-card.skel { pointer-events:none; }
|
|
17485
|
+
.site-card.skel:hover { transform:none; box-shadow:none; border-color:var(--line); }
|
|
17486
|
+
.site-card.skel .si.skel-b { background:var(--surface-2); }
|
|
17487
|
+
.site-card.skel .skel-t { height:14px; width:calc(40% + var(--i, 0) * 8%); border-radius:5px; margin-bottom:6px; }
|
|
17488
|
+
.site-card.skel .skel-s { height:11px; width:calc(55% + var(--i, 0) * 6%); border-radius:4px; }
|
|
17489
|
+
.site-card.skel .skel-addr { height:30px; width:80%; border-radius:var(--radius-sm); }
|
|
17490
|
+
.filerow.skel { pointer-events:none; }
|
|
17491
|
+
.filerow.skel .fi.skel-b { background:var(--surface-2); }
|
|
17492
|
+
.filerow.skel .skel-t { height:13px; width:calc(36% + var(--i, 0) * 10%); border-radius:4px; }
|
|
17493
|
+
.blob-hero.skel .blob-legend .skel-s { height:12px; width:calc(70% + var(--i, 0) * 8%); }
|
|
17494
|
+
.blob-hero.skel .blob-total .skel-s { display:block; height:11px; width:14ch; }
|
|
17495
|
+
.skel-b { display:block; position:relative; overflow:hidden; background:var(--surface-2); border-radius:6px; }
|
|
17496
|
+
.skel-b::after { content:""; position:absolute; inset:0;
|
|
17497
|
+
background:linear-gradient(90deg, transparent 20%, color-mix(in srgb, var(--fg) 5%, transparent) 50%, transparent 80%);
|
|
17498
|
+
background-size:200% 100%; animation:skel-sweep 1.6s ease-in-out infinite;
|
|
17499
|
+
animation-delay:calc(var(--i, 0) * 120ms); }
|
|
17500
|
+
.shv-row.skel { pointer-events:none; }
|
|
17501
|
+
.shv-row.skel:hover { border-color:var(--line); box-shadow:none; }
|
|
17502
|
+
.skel-t { height:1.55em; height:1lh; width:calc(52% + var(--i, 0) * 9%); border-radius:5px; }
|
|
17503
|
+
.skel-s { height:1.55em; height:1lh; width:min(44ch, 60%); border-radius:4px; }
|
|
17504
|
+
.shv-status .skel-st { height:1.55em; height:1lh; width:7ch; border-radius:4px; }
|
|
17505
|
+
.skel-btn { width:76px; height:29px; border-radius:var(--radius-sm); }
|
|
17506
|
+
.skel-ib { width:30px; height:30px; border-radius:var(--radius-sm); }
|
|
17507
|
+
@keyframes skel-in { from { opacity:0; } to { opacity:1; } }
|
|
17508
|
+
@keyframes skel-sweep { from { background-position:100% 0; } to { background-position:-100% 0; } }
|
|
17202
17509
|
@media (max-width:640px){
|
|
17203
|
-
|
|
17510
|
+
.shv-actions .skel-btn { flex:1; width:auto; height:36px; }
|
|
17511
|
+
.shv-actions .skel-ib { width:40px; height:36px; }
|
|
17512
|
+
/* head stacks: title/desc full row, then segmented tabs, then [source][search] */
|
|
17204
17513
|
.shares-page .db-head { flex-direction:column; align-items:stretch; gap:12px; }
|
|
17205
|
-
.shv-
|
|
17206
|
-
.shv-
|
|
17207
|
-
.shv-seg
|
|
17208
|
-
.shv-
|
|
17209
|
-
.shv-
|
|
17210
|
-
|
|
17211
|
-
|
|
17212
|
-
|
|
17213
|
-
.shv-
|
|
17214
|
-
|
|
17215
|
-
.shv-
|
|
17216
|
-
|
|
17217
|
-
|
|
17218
|
-
.shv-actions {
|
|
17219
|
-
.shv-actions .btn { flex:1;
|
|
17514
|
+
.shv-toolbar { flex-wrap:wrap; }
|
|
17515
|
+
.shv-tools-right { display:contents; }
|
|
17516
|
+
.shv-seg { display:flex; width:100%; order:-1; }
|
|
17517
|
+
.shv-seg-btn { flex:1; justify-content:center; padding:6px 4px; }
|
|
17518
|
+
.shv-src { flex:1; justify-content:center; }
|
|
17519
|
+
.shv-search { flex:1; min-width:0; max-width:none; }
|
|
17520
|
+
|
|
17521
|
+
/* rows: ico + main on top; status, then the action row, each full width */
|
|
17522
|
+
.shv-row { grid-template-columns:38px minmax(0,1fr); gap:8px 13px; align-items:start; }
|
|
17523
|
+
.shv-title { white-space:normal; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
|
|
17524
|
+
.shv-sub { white-space:normal; flex-wrap:wrap; row-gap:2px; }
|
|
17525
|
+
.shv-link { max-width:100%; }
|
|
17526
|
+
.shv-status { grid-column:1 / -1; min-width:0; }
|
|
17527
|
+
.shv-actions { grid-column:1 / -1; }
|
|
17528
|
+
.shv-actions .btn { flex:1; padding:8px 10px; }
|
|
17529
|
+
.shv-actions .iconbtn { width:40px; height:36px; flex:none; }
|
|
17530
|
+
.shv-group-head { flex-wrap:wrap; }
|
|
17531
|
+
.shv-group-head .btn { margin-left:0; width:100%; justify-content:center; padding:7px 10px; }
|
|
17220
17532
|
}
|
|
17221
17533
|
|
|
17222
17534
|
/* in-context "\u5DF2\u5206\u4EAB" badge on docs / tables / site cards */
|
|
@@ -17901,6 +18213,7 @@ var styles_default = ` :root {
|
|
|
17901
18213
|
html:has(body.mobile), body.mobile { background:var(--sidebar); }
|
|
17902
18214
|
html:has(body.mobile-content), body.mobile-content { background:var(--bg); }
|
|
17903
18215
|
.sb-head .iconbtn { display:none; } /* no collapse on mobile */
|
|
18216
|
+
.sb-head .sync-ind { display:inline-flex; height:32px; } /* .brand's flex:1 already pushes it right */
|
|
17904
18217
|
.sb-resizer { display:none; }
|
|
17905
18218
|
.topbar .hamburger { display:grid; } /* serves as the back button */
|
|
17906
18219
|
/* site config topbar: icon-only publish button so the crumb keeps its last segment */
|
|
@@ -18358,6 +18671,12 @@ var styles_default = ` :root {
|
|
|
18358
18671
|
.blob-dot.keep { background:var(--line-strong); }
|
|
18359
18672
|
.blob-dot.pin { background:var(--muted); opacity:.6; }
|
|
18360
18673
|
.blob-total { margin:13px 0 14px; font-size:var(--fs-xs); color:var(--muted); }
|
|
18674
|
+
/* headline number over the legend (\u6570\u636E\u4E0E\u5907\u4EFD \xB7 \u9644\u4EF6) */
|
|
18675
|
+
.blob-stat { font-size:20px; font-weight:700; letter-spacing:-.02em; font-variant-numeric:tabular-nums; margin-bottom:10px; }
|
|
18676
|
+
.blob-stat small { font-size:var(--fs-sm); font-weight:500; color:var(--muted); margin-left:8px; letter-spacing:0; }
|
|
18677
|
+
/* a status sentence in the action row (never a disabled button) */
|
|
18678
|
+
.blob-actions .blob-status { display:inline-flex; align-items:center; gap:6px; font-size:var(--fs-sm); color:var(--muted); }
|
|
18679
|
+
.blob-actions .blob-status .ico { width:14px; height:14px; color:var(--success); }
|
|
18361
18680
|
|
|
18362
18681
|
.blob-ring { position:relative; width:152px; height:152px; flex:none; }
|
|
18363
18682
|
.blob-ring-svg { width:100%; height:100%; display:block; }
|
|
@@ -18392,7 +18711,7 @@ var styles_default = ` :root {
|
|
|
18392
18711
|
}
|
|
18393
18712
|
|
|
18394
18713
|
@media (prefers-reduced-motion: reduce) {
|
|
18395
|
-
.switch, .switch-knob, .ico.spin, .blob-seg, .blob-ring-track { transition:none; animation:none; }
|
|
18714
|
+
.switch, .switch-knob, .ico.spin, .sync-ring, .blob-seg, .blob-ring-track { transition:none; animation:none; }
|
|
18396
18715
|
}
|
|
18397
18716
|
|
|
18398
18717
|
/* Blob manager popup (Settings storage \u2192 Blob \u7BA1\u7406): a sortable/filterable list
|
|
@@ -19270,15 +19589,49 @@ function recordFieldHistory(db, id, propId) {
|
|
|
19270
19589
|
// src/core/audit.ts
|
|
19271
19590
|
init_crdt();
|
|
19272
19591
|
init_errors();
|
|
19592
|
+
|
|
19593
|
+
// src/core/tables.ts
|
|
19594
|
+
var TABLES = {
|
|
19595
|
+
databases: { tier: "content", synced: true },
|
|
19596
|
+
properties: { tier: "content", synced: true },
|
|
19597
|
+
records: { tier: "content", synced: true },
|
|
19598
|
+
documents: { tier: "content", synced: true },
|
|
19599
|
+
doc_blocks: { tier: "content", synced: true },
|
|
19600
|
+
sites: { tier: "content", synced: true },
|
|
19601
|
+
site_files: { tier: "content", synced: true },
|
|
19602
|
+
site_channels: { tier: "system", synced: true },
|
|
19603
|
+
blob_policy: { tier: "system", synced: true },
|
|
19604
|
+
nodes: { tier: "system", synced: true },
|
|
19605
|
+
meta: { tier: "local", synced: false },
|
|
19606
|
+
crdt_changes: { tier: "local", synced: false },
|
|
19607
|
+
peers: { tier: "local", synced: false },
|
|
19608
|
+
storage_cursors: { tier: "local", synced: false },
|
|
19609
|
+
room_rows: { tier: "local", synced: false },
|
|
19610
|
+
peer_grants: { tier: "local", synced: false },
|
|
19611
|
+
pairing_codes: { tier: "local", synced: false },
|
|
19612
|
+
shares: { tier: "local", synced: false },
|
|
19613
|
+
site_channel_observations: { tier: "local", synced: false },
|
|
19614
|
+
blob_cache: { tier: "local", synced: false },
|
|
19615
|
+
drop_rejects: { tier: "local", synced: false },
|
|
19616
|
+
search_fts: { tier: "local", synced: false }
|
|
19617
|
+
};
|
|
19618
|
+
var names = (pred) => Object.entries(TABLES).filter(([, s]) => pred(s)).map(([n]) => n);
|
|
19619
|
+
var SYNCED_TABLES = names((s) => s.synced);
|
|
19620
|
+
var CONTENT_TABLES = names((s) => s.tier === "content");
|
|
19621
|
+
var SYSTEM_DATASETS = names((s) => s.tier === "system");
|
|
19622
|
+
var LOCAL_TABLES = names((s) => s.tier === "local");
|
|
19623
|
+
|
|
19624
|
+
// src/core/audit.ts
|
|
19273
19625
|
function isMetaCol(dataset, col) {
|
|
19274
19626
|
if (dataset === "records")
|
|
19275
19627
|
return RECORD_META.has(col);
|
|
19276
19628
|
return col === "created_hlc" || col === "__deleted";
|
|
19277
19629
|
}
|
|
19278
19630
|
function oplogExclude() {
|
|
19631
|
+
const excluded = [...OPLOG_ONLY_DATASETS, ...SYSTEM_DATASETS];
|
|
19279
19632
|
return {
|
|
19280
|
-
sql:
|
|
19281
|
-
params:
|
|
19633
|
+
sql: excluded.map(() => "dataset != ?"),
|
|
19634
|
+
params: excluded
|
|
19282
19635
|
};
|
|
19283
19636
|
}
|
|
19284
19637
|
function changesOfTxn(db, txn, beforeHlc) {
|
|
@@ -22468,8 +22821,11 @@ var FieldHistoryEntrySchema = object({
|
|
|
22468
22821
|
});
|
|
22469
22822
|
var NodeInfoSchema = object({
|
|
22470
22823
|
node_id: string2(),
|
|
22471
|
-
label: string2().nullable().describe("
|
|
22472
|
-
self: boolean2().describe("True for the node serving this request")
|
|
22824
|
+
label: string2().nullable().describe("Synced roster name \u2192 pairing label \u2192 null when unnamed"),
|
|
22825
|
+
self: boolean2().describe("True for the node serving this request"),
|
|
22826
|
+
platform: _enum(["macos", "windows", "linux", "ios", "android", "web"]).nullable(),
|
|
22827
|
+
form: _enum(["laptop", "desktop", "phone", "server", "browser"]).nullable(),
|
|
22828
|
+
app: _enum(["cli", "server", "desktop", "web"]).nullable()
|
|
22473
22829
|
});
|
|
22474
22830
|
var CacheStatsSchema = object({
|
|
22475
22831
|
totalBytes: number2(),
|
|
@@ -22979,13 +23335,17 @@ var webuiRoutes = [
|
|
|
22979
23335
|
{
|
|
22980
23336
|
method: "PATCH",
|
|
22981
23337
|
path: "/api/node",
|
|
22982
|
-
summary: "
|
|
22983
|
-
request: object({
|
|
23338
|
+
summary: "Rename a device (default: this one). Writes the synced roster; empty/null clears",
|
|
23339
|
+
request: object({
|
|
23340
|
+
label: string2().nullable().optional(),
|
|
23341
|
+
node_id: string2().optional().describe("Target node; omitted = this device")
|
|
23342
|
+
}),
|
|
22984
23343
|
response: NodeInfoSchema,
|
|
22985
23344
|
handler: handle3(async (req, { db }) => {
|
|
22986
23345
|
const body = await req.json();
|
|
22987
|
-
|
|
22988
|
-
|
|
23346
|
+
const id = body.node_id ?? getNodeId(db);
|
|
23347
|
+
setNodeLabel(db, body.label ?? null, id);
|
|
23348
|
+
return displayNodes(db).find((n) => n.node_id === id);
|
|
22989
23349
|
})
|
|
22990
23350
|
},
|
|
22991
23351
|
{
|
|
@@ -23139,12 +23499,14 @@ export {
|
|
|
23139
23499
|
createSite,
|
|
23140
23500
|
dbPath,
|
|
23141
23501
|
decodeFileRow,
|
|
23502
|
+
defaultForm,
|
|
23142
23503
|
deleteBlob,
|
|
23143
23504
|
deleteDatabase,
|
|
23144
23505
|
deleteDocument,
|
|
23145
23506
|
deleteFile,
|
|
23146
23507
|
deleteRecord,
|
|
23147
23508
|
deleteSite,
|
|
23509
|
+
describeSelf,
|
|
23148
23510
|
displayNodes,
|
|
23149
23511
|
documentTitleMap,
|
|
23150
23512
|
documentVersion,
|
|
@@ -23190,6 +23552,7 @@ export {
|
|
|
23190
23552
|
listDatabases,
|
|
23191
23553
|
listDocuments,
|
|
23192
23554
|
listFiles,
|
|
23555
|
+
listNodeMeta,
|
|
23193
23556
|
listProperties,
|
|
23194
23557
|
listRecords,
|
|
23195
23558
|
listSites,
|
|
@@ -23200,6 +23563,7 @@ export {
|
|
|
23200
23563
|
metahubHome,
|
|
23201
23564
|
migrateDocBlocks,
|
|
23202
23565
|
migrateDocuments,
|
|
23566
|
+
migrateNodes,
|
|
23203
23567
|
migrateOplog,
|
|
23204
23568
|
migratePeers,
|
|
23205
23569
|
migrateRecords,
|
|
@@ -23208,12 +23572,14 @@ export {
|
|
|
23208
23572
|
moveRecord,
|
|
23209
23573
|
newId,
|
|
23210
23574
|
nextHlc,
|
|
23575
|
+
nodeLabelOf,
|
|
23211
23576
|
normalizeSiteName,
|
|
23212
23577
|
normalizeSitePath,
|
|
23213
23578
|
observeHlc,
|
|
23214
23579
|
openMetahub,
|
|
23215
23580
|
parseDocBlocks,
|
|
23216
23581
|
parseHlc,
|
|
23582
|
+
platformFromProcess,
|
|
23217
23583
|
prepareRecordCells,
|
|
23218
23584
|
prependDocument,
|
|
23219
23585
|
publishDirectory,
|
|
@@ -23222,6 +23588,7 @@ export {
|
|
|
23222
23588
|
putFile,
|
|
23223
23589
|
putFileInline,
|
|
23224
23590
|
randomSuffix,
|
|
23591
|
+
readNodeMeta,
|
|
23225
23592
|
rebuildSearchIndex,
|
|
23226
23593
|
reconcile,
|
|
23227
23594
|
recordTitleMap,
|
|
@@ -23267,4 +23634,4 @@ export {
|
|
|
23267
23634
|
writeFileRow
|
|
23268
23635
|
};
|
|
23269
23636
|
|
|
23270
|
-
//# debugId=
|
|
23637
|
+
//# debugId=189D146DD6185A7B64756E2164756E21
|