@kedem/okdb 1.0.1 → 1.0.4
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/bin/okdb.js +1 -1
- package/keys/public.pem +3 -0
- package/okdb.js +1 -1
- package/package.json +2 -2
- package/public/admin-app.js +1 -1
- package/public/components/admin-route-host.ok.js +1 -1
- package/public/components/env-picker.ok.js +1 -1
- package/public/components/field-path-input.ok.js +1 -1
- package/public/components/field.html +154 -2
- package/public/components/geo-map.ok.js +1 -1
- package/public/components/ok-cron-input.ok.js +1 -1
- package/public/components/ok-dropzone.ok.js +1 -1
- package/public/components/ok-js-input.ok.js +1 -1
- package/public/components/ok-json-input.ok.js +1 -1
- package/public/components/ok-text-monaco-input.ok.js +1 -1
- package/public/components/theme-toggle.ok.js +1 -1
- package/public/index.html +29 -2
- package/public/layouts/_default.ok.html +1 -1
- package/public/layouts/okdb-cube.ok.html +1 -1
- package/public/login-app.js +1 -1
- package/public/login.html +205 -1
- package/public/modules/ok.js +1 -1
- package/public/sections/api/index.ok.html +1 -1
- package/public/sections/api/parts/api-endpoint.ok.js +1 -1
- package/public/sections/auth/index.ok.html +1 -1
- package/public/sections/auth/modals/create-token-modal.ok.js +1 -1
- package/public/sections/auth/modals/edit-token-modal.ok.js +1 -1
- package/public/sections/auth/modals/perm-info-popover.ok.js +1 -1
- package/public/sections/auth/parts/token-detail.ok.js +1 -1
- package/public/sections/auth/parts/token-list.ok.js +1 -1
- package/public/sections/db/index.ok.html +1 -1
- package/public/sections/db/modals/add-item-modal.ok.js +1 -1
- package/public/sections/db/modals/create-env-modal.ok.js +1 -1
- package/public/sections/db/modals/create-fts-modal.ok.js +1 -1
- package/public/sections/db/modals/create-index-modal.ok.js +1 -1
- package/public/sections/db/modals/create-pipeline-modal.ok.js +1 -1
- package/public/sections/db/modals/demo-env-modal.ok.js +1 -1
- package/public/sections/db/modals/remove-env-modal.ok.js +1 -1
- package/public/sections/db/modals/schema-edit-modal.ok.js +1 -1
- package/public/sections/db/parts/db-overview.ok.js +1 -1
- package/public/sections/db/parts/db-pipelines-panel.ok.js +1 -1
- package/public/sections/db/parts/edit-row.ok.js +1 -1
- package/public/sections/db/parts/tm-timeline.ok.js +1 -1
- package/public/sections/db/parts/type-changelog.ok.js +1 -1
- package/public/sections/db/parts/type-list.ok.js +1 -1
- package/public/sections/db/parts/type-pipelines-tab.ok.js +1 -1
- package/public/sections/db/parts/type-schema-tab.ok.js +1 -1
- package/public/sections/db/parts/type-viewer.ok.js +1 -1
- package/public/sections/db/parts/type-violations-tab.ok.js +1 -1
- package/public/sections/docs/index.ok.html +1 -1
- package/public/sections/embeddings/index.ok.html +1 -1
- package/public/sections/embeddings/modals/register-model-modal.ok.js +1 -1
- package/public/sections/embeddings/parts/embed-create-panel.ok.js +1 -1
- package/public/sections/embeddings/parts/embed-embedder-panel.ok.js +1 -1
- package/public/sections/embeddings/parts/embed-engine-declaration-editor.ok.js +1 -1
- package/public/sections/embeddings/parts/embed-engine-list.ok.js +1 -1
- package/public/sections/embeddings/parts/embed-indexer-panel.ok.js +1 -1
- package/public/sections/embeddings/parts/embed-lifecycle-bar.ok.js +1 -1
- package/public/sections/embeddings/parts/embed-model-catalog.ok.js +1 -1
- package/public/sections/embeddings/parts/embed-overview.ok.js +1 -1
- package/public/sections/embeddings/parts/embed-search-panel.ok.js +1 -1
- package/public/sections/embeddings/parts/embed-worker-panel.ok.js +1 -1
- package/public/sections/embeddings/parts/embeddings-engine-panel.ok.js +1 -1
- package/public/sections/embeddings/parts/engines-overview.ok.js +1 -1
- package/public/sections/embeddings/parts/pipeline-create-panel.ok.js +1 -1
- package/public/sections/engines/engine-panel-shared-style.js +1 -1
- package/public/sections/engines/engine-ui-utils.js +1 -1
- package/public/sections/engines/index.ok.html +1 -1
- package/public/sections/engines/modals/engine-modal.ok.js +1 -1
- package/public/sections/engines/parts/engine-declaration-editor.ok.js +1 -1
- package/public/sections/engines/parts/engine-lifecycle-actions.ok.js +1 -1
- package/public/sections/engines/parts/engine-panel-shell.ok.js +1 -1
- package/public/sections/engines/parts/engine-resource-card.ok.js +1 -1
- package/public/sections/engines/parts/engine-row-item.ok.js +1 -1
- package/public/sections/engines/parts/generic-engine-panel.ok.js +1 -1
- package/public/sections/files/index.ok.html +1 -1
- package/public/sections/files/parts/files-blob-status.ok.js +1 -1
- package/public/sections/files/parts/files-collection-list.ok.js +1 -1
- package/public/sections/files/parts/files-collection-panel.ok.js +1 -1
- package/public/sections/files/parts/files-file-list.ok.js +1 -1
- package/public/sections/files/parts/files-overview.ok.js +1 -1
- package/public/sections/files/parts/files-path-browser.ok.js +1 -1
- package/public/sections/files/parts/files-upload-panel.ok.js +1 -1
- package/public/sections/functions/index.ok.html +1 -1
- package/public/sections/functions/modals/function-modal.ok.js +1 -1
- package/public/sections/functions/parts/function-detail.ok.js +1 -1
- package/public/sections/functions/parts/function-logs.ok.js +1 -1
- package/public/sections/functions/parts/function-runner.ok.js +1 -1
- package/public/sections/functions/parts/functions-list.ok.js +1 -1
- package/public/sections/license/index.ok.html +1 -1
- package/public/sections/license/parts/license-panel.ok.js +1 -1
- package/public/sections/processors/index.ok.html +1 -1
- package/public/sections/queue/index.ok.html +1 -1
- package/public/sections/queue/modals/create-bucket-modal.ok.js +1 -1
- package/public/sections/queue/modals/edit-bucket-modal.ok.js +1 -1
- package/public/sections/queue/modals/edit-job-modal.ok.js +1 -1
- package/public/sections/queue/modals/enqueue-job-modal.ok.js +1 -1
- package/public/sections/queue/modals/job-type-modal.ok.js +1 -1
- package/public/sections/queue/parts/ok-job-form.ok.js +1 -1
- package/public/sections/queue/parts/queue-buckets.ok.js +1 -1
- package/public/sections/queue/parts/queue-job-types.ok.js +1 -1
- package/public/sections/queue/parts/queue-jobs.ok.js +1 -1
- package/public/sections/queue/parts/queue-types.ok.js +1 -1
- package/public/sections/queue/parts/queue-workers.ok.js +1 -1
- package/public/sections/sync/index.ok.html +1 -1
- package/public/sections/sync/parts/sync-node-detail.ok.js +1 -1
- package/public/sections/sync/parts/sync-retention.ok.js +1 -1
- package/public/sections/sync/parts/sync-topology.ok.js +1 -1
- package/public/sections/system/index.ok.html +1 -1
- package/public/sections/system/modals/data-ops-modal.ok.js +1 -1
- package/public/sections/system/parts/system-api-panel.ok.js +1 -1
- package/public/sections/system/parts/system-auth-panel.ok.js +1 -1
- package/public/sections/system/parts/system-log-history.ok.js +1 -1
- package/public/sections/system/parts/system-log.ok.js +1 -1
- package/public/sections/system/parts/system-status.ok.js +1 -1
- package/public/sections/system/parts/system-sync-panel.ok.js +1 -1
- package/public/sections/views/index.ok.html +1 -1
- package/public/sections/views/modals/create-view-modal.ok.js +1 -1
- package/public/sections/views/parts/view-detail.ok.js +1 -1
- package/public/sections/views/parts/views-list.ok.js +1 -1
- package/public/sections/views/parts/views-overview.ok.js +1 -1
- package/public/setup-app.js +1 -1
- package/public/setup.html +189 -1
- package/public/stores/auth-store.js +1 -1
- package/public/stores/events-store.js +1 -1
- package/public/stores/functions-store.js +1 -1
- package/public/stores/okdb-store.js +1 -1
- package/public/stores/theme-store.js +1 -1
- package/public/test-cron-input.html +53 -3
- package/public/test-json-input.html +182 -3
- package/public/util/permissions.js +1 -1
- package/public/util/sift-schema.js +1 -1
- package/public/vendor/okjs/ok.esm.min.js +1 -2
- package/public/view-models/type-query-vm.js +1 -1
- package/okdb-data/default/data.mdb +0 -0
- package/okdb-data/default/lock.mdb +0 -0
- package/okdb-data/~log/data.mdb +0 -0
- package/okdb-data/~log/lock.mdb +0 -0
- package/okdb-data/~system/data.mdb +0 -0
- package/okdb-data/~system/lock.mdb +0 -0
- package/public/images/logo.png +0 -0
- package/public/images/logo.svg +0 -333
|
@@ -1 +1 @@
|
|
|
1
|
-
function _0x15a2(){const _0x2ceb39=['setItem','1163368RBnNTG','externalSelectedNodeId','topology','_mousemoveCb','envs','node','node_hi','_recomputeLayout','hoveredLinkKey','fresh-stale','\x20ln-disabled','left','selfId','cos','/api/sync/reconcile-peer','lo_to_hi','_customPos','zoom','type','refreshTopology','application/json','lag-med','status','/api/sync/links/','selectLink','few\x20seconds\x20ago','_onSvgMousedown','direction','includes','json','wheel','\x20nd-lag','min','\x20→\x20','set','rec','\x20nd-critical','sin','_autoRefreshTimer','Remove\x20this\x20data\x20link?\x20Nodes\x20stay\x20connected\x20but\x20data\x20will\x20stop\x20syncing.','addEventListener','now','\x20nd-stale','$dispatch','has','both','translate(','length','idle','indexOf','disabled','\x20ln-lag-high','_onMouseup','panY','lo_pull','push','floor','\x20nd-hover','stringify','select','startsWith','selectedLinkKey','all','hoveredNodeId','color:var(--dis);opacity:0.7','_wheelCb','autoRefresh','lag-high','mousedown','hi_to_lo','isLinkInteractable','Save\x20failed','_onWheel','last_seen','removeEventListener','style','_mousedownCb','disconnect','3425260gFlLng','reconciling','height','577550oIposY','key','stale','12256006OnHxhv','.topo-g','fresh-ok','transform','loadingTopology','pointer-events:none','_effectivePos','editingLink','slice','okdb-topo-pos','savingLink','last_error','_healthMarker','enabled','backlog',')\x20scale(','588201pyoymB','nodeRadius','$attr','preventDefault','_groupEl','nodes','color:var(--ok)','getItem','/api/sync/reconcile','svg','hi_pull','get','lastUpdatedAt','_drag','_savePositions','map','mousemove','/api/sync/topology','available_envs','\x20ln-stale','_applyTransform','parse','\x20nd-dim','_svgEl','canon','visibilitychange','sort','getBoundingClientRect','clientY','message','\x20(mesh)','mouseup','links','removeItem','layout','sqrt','linkEditEnabled','url(#arrow-rev-','_ro','refresh\x20failed','selected_node_id','$el','\x20ln-rec','_mouseupCb','selectedNodeId','address','url(#arrow-disabled)','pan','_px2vb','lagging','self_id','clock','fitView','critical','querySelector','find','width','POST','error','deltaY','nodeHealth','clientX','$watch','\x20ln-err','writeText','availableEnvs','8157420IhQrpP','forEach','linkEditEnvs','clipboard','35145ytZcsZ','behind','\x20L\x20','delete','top','panX','filter','url(#arrow-','values','grabbing','_pullHealth','node_lo','\x20ln-dim','_visibilityHandler','parentElement','grab','cursor','_tick','reconcilingTarget','_measure','observe','max','border-color:var(--dis);color:var(--dis)','linkEditDir','link','DELETE','setAttribute','border-color:var(--accent);color:var(--accent);background:var(--bg)','pos','\x20⇄\x20','fresh-now','<div\x20style=\x22--bg:var(--okdb-app-bg-deep);\x20--panel:var(--okdb-app-surface);\x20--panel2:var(--okdb-app-bg-deep);\x20--border:var(--okdb-app-border);\x20--border2:var(--okdb-app-surface-2);\x0a\x20\x20--text:var(--okdb-app-fg);\x20--muted:var(--okdb-app-fg-muted);\x20--accent:var(--okdb-accent);\x20--ok:var(--okdb-success);\x20--rec:var(--okdb-accent);\x0a\x20\x20--warn:var(--okdb-warning);\x20--bad:var(--okdb-danger);\x20--err:var(--okdb-danger);\x20--dis:var(--okdb-app-fg-subtle);\x0a\x20\x20border:1px\x20solid\x20var(--border);\x20border-radius:12px;\x20background:var(--bg);\x20color:var(--text);\x0a\x20\x20display:flex;\x20flex-direction:column;\x20flex:1;\x20position:relative;\x20overflow:hidden;\x0a\x20\x20font-family:\x20ui-monospace,\x20monospace;\x22><div\x20style=\x22padding:12px\x2014px;display:flex;justify-content:space-between;border-bottom:1px\x20solid\x20var(--border2);background:var(--panel);align-items:center;\x22><div\x20style=\x22display:flex;align-items:center;gap:10px;\x22><div\x20style=\x22font-weight:700;letter-spacing:0.8px;color:var(--accent);\x22>NETWORK_TOPOLOGY</div><button\x20@click=\x22reconcileAll()\x22\x20:disabled=\x22reconciling\x22\x20style=\x22font-size:11px;padding:4px\x2010px;border-radius:8px;border:1px\x20solid\x20var(--border);background:var(--panel2);color:var(--accent);cursor:pointer;\x22><span\x20style=\x22display:flex;align-items:center;gap:6px;\x22><span\x20:style=\x22reconcilingTarget===\x27all\x27?\x27display:inline-block\x27:\x27display:none\x27\x22\x20class=\x22spinner\x22/>\x20{{reconcilingTarget===\x27all\x27?\x27RECONCILING\x27:\x27RECONCILE\x20ALL\x27}}</span></button>\x20<button\x20@click=\x22refreshTopology()\x22\x20:disabled=\x22loadingTopology\x22\x20style=\x22font-size:11px;padding:4px\x2010px;border-radius:8px;border:1px\x20solid\x20var(--border);background:var(--panel2);color:var(--muted);cursor:pointer;\x22>REFRESH</button>\x20<button\x20@click=\x22fitView()\x22\x20style=\x22font-size:11px;padding:4px\x2010px;border-radius:8px;border:1px\x20solid\x20var(--border);background:var(--panel2);color:var(--muted);cursor:pointer;\x22\x20title=\x22Fit\x20all\x20nodes\x20into\x20view\x22>FIT</button>\x20<button\x20@click=\x22resetLayout()\x22\x20style=\x22font-size:11px;padding:4px\x2010px;border-radius:8px;border:1px\x20solid\x20var(--border);background:var(--panel2);color:var(--muted);cursor:pointer;\x22\x20title=\x22Reset\x20node\x20positions\x22>RESET</button>\x20<span\x20:style=\x22{opacity:loadingTopology?1:0}\x22\x20class=\x22spinner\x22/></div><div\x20style=\x22display:flex;align-items:center;gap:12px;font-size:11px;\x22><span\x20:class=\x22freshnessClass\x22>updated:\x20{{freshnessLabel}}</span>\x20<span\x20style=\x22opacity:0.75;\x22>{{nodes.length}}\x20NODES\x20//\x20{{links.length}}\x20LINKS</span></div></div><div\x20style=\x22flex:1;position:relative;\x22><svg\x20:viewBox=\x22\x270\x200\x20\x27+w+\x27\x20\x27+h\x22\x20style=\x22width:100%;height:100%;\x22><rect\x20:width=\x22w\x22\x20:height=\x22h\x22\x20fill=\x22transparent\x22\x20style=\x22pointer-events:all\x22\x20@click=\x22deselect()\x22/><defs><marker\x20id=\x22arrow-ok\x22\x20viewBox=\x220\x200\x2010\x2010\x22\x20refX=\x2210\x22\x20refY=\x225\x22\x20markerWidth=\x226\x22\x20markerHeight=\x226\x22\x20orient=\x22auto\x22><path\x20d=\x22M\x200\x200\x20L\x2010\x205\x20L\x200\x2010\x20z\x22\x20fill=\x22var(--ok)\x22/></marker><marker\x20id=\x22arrow-rec\x22\x20viewBox=\x220\x200\x2010\x2010\x22\x20refX=\x2210\x22\x20refY=\x225\x22\x20markerWidth=\x226\x22\x20markerHeight=\x226\x22\x20orient=\x22auto\x22><path\x20d=\x22M\x200\x200\x20L\x2010\x205\x20L\x200\x2010\x20z\x22\x20fill=\x22var(--rec)\x22/></marker><marker\x20id=\x22arrow-err\x22\x20viewBox=\x220\x200\x2010\x2010\x22\x20refX=\x2210\x22\x20refY=\x225\x22\x20markerWidth=\x226\x22\x20markerHeight=\x226\x22\x20orient=\x22auto\x22><path\x20d=\x22M\x200\x200\x20L\x2010\x205\x20L\x200\x2010\x20z\x22\x20fill=\x22var(--err)\x22/></marker><marker\x20id=\x22arrow-lag-med\x22\x20viewBox=\x220\x200\x2010\x2010\x22\x20refX=\x2210\x22\x20refY=\x225\x22\x20markerWidth=\x226\x22\x20markerHeight=\x226\x22\x20orient=\x22auto\x22><path\x20d=\x22M\x200\x200\x20L\x2010\x205\x20L\x200\x2010\x20z\x22\x20fill=\x22var(--warn)\x22/></marker><marker\x20id=\x22arrow-lag-high\x22\x20viewBox=\x220\x200\x2010\x2010\x22\x20refX=\x2210\x22\x20refY=\x225\x22\x20markerWidth=\x226\x22\x20markerHeight=\x226\x22\x20orient=\x22auto\x22><path\x20d=\x22M\x200\x200\x20L\x2010\x205\x20L\x200\x2010\x20z\x22\x20fill=\x22var(--bad)\x22/></marker><marker\x20id=\x22arrow-disabled\x22\x20viewBox=\x220\x200\x2010\x2010\x22\x20refX=\x2210\x22\x20refY=\x225\x22\x20markerWidth=\x226\x22\x20markerHeight=\x226\x22\x20orient=\x22auto\x22><path\x20d=\x22M\x200\x200\x20L\x2010\x205\x20L\x200\x2010\x20z\x22\x20fill=\x22var(--dis)\x22/></marker><marker\x20id=\x22arrow-rev-ok\x22\x20viewBox=\x220\x200\x2010\x2010\x22\x20refX=\x2210\x22\x20refY=\x225\x22\x20markerWidth=\x226\x22\x20markerHeight=\x226\x22\x20orient=\x22auto-start-reverse\x22><path\x20d=\x22M\x200\x200\x20L\x2010\x205\x20L\x200\x2010\x20z\x22\x20fill=\x22var(--ok)\x22/></marker><marker\x20id=\x22arrow-rev-rec\x22\x20viewBox=\x220\x200\x2010\x2010\x22\x20refX=\x2210\x22\x20refY=\x225\x22\x20markerWidth=\x226\x22\x20markerHeight=\x226\x22\x20orient=\x22auto-start-reverse\x22><path\x20d=\x22M\x200\x200\x20L\x2010\x205\x20L\x200\x2010\x20z\x22\x20fill=\x22var(--rec)\x22/></marker><marker\x20id=\x22arrow-rev-err\x22\x20viewBox=\x220\x200\x2010\x2010\x22\x20refX=\x2210\x22\x20refY=\x225\x22\x20markerWidth=\x226\x22\x20markerHeight=\x226\x22\x20orient=\x22auto-start-reverse\x22><path\x20d=\x22M\x200\x200\x20L\x2010\x205\x20L\x200\x2010\x20z\x22\x20fill=\x22var(--err)\x22/></marker><marker\x20id=\x22arrow-rev-lag-med\x22\x20viewBox=\x220\x200\x2010\x2010\x22\x20refX=\x2210\x22\x20refY=\x225\x22\x20markerWidth=\x226\x22\x20markerHeight=\x226\x22\x20orient=\x22auto-start-reverse\x22><path\x20d=\x22M\x200\x200\x20L\x2010\x205\x20L\x200\x2010\x20z\x22\x20fill=\x22var(--warn)\x22/></marker><marker\x20id=\x22arrow-rev-lag-high\x22\x20viewBox=\x220\x200\x2010\x2010\x22\x20refX=\x2210\x22\x20refY=\x225\x22\x20markerWidth=\x226\x22\x20markerHeight=\x226\x22\x20orient=\x22auto-start-reverse\x22><path\x20d=\x22M\x200\x200\x20L\x2010\x205\x20L\x200\x2010\x20z\x22\x20fill=\x22var(--bad)\x22/></marker><marker\x20id=\x22arrow-rev-disabled\x22\x20viewBox=\x220\x200\x2010\x2010\x22\x20refX=\x2210\x22\x20refY=\x225\x22\x20markerWidth=\x226\x22\x20markerHeight=\x226\x22\x20orient=\x22auto-start-reverse\x22><path\x20d=\x22M\x200\x200\x20L\x2010\x205\x20L\x200\x2010\x20z\x22\x20fill=\x22var(--dis)\x22/></marker></defs><style>.ln\x20{\x20fill:none;\x20stroke-width:1.9;\x20opacity:0.70;\x20transition:opacity\x200.14s,stroke-width\x200.14s,filter\x200.14s;\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.ln-ok\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{\x20stroke:var(--ok);\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.ln-behind-light\x20{\x20stroke:var(--okdb-info);\x20opacity:0.8;\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.ln-rec\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{\x20stroke:var(--rec);\x20stroke-dasharray:4\x203;\x20animation:dash\x201.1s\x20linear\x20infinite;\x20filter:drop-shadow(0\x200\x204px\x20rgba(77,163,255,0.5));\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.ln-lag-med\x20\x20\x20\x20\x20\x20{\x20stroke:var(--warn);\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.ln-lag-high\x20\x20\x20\x20\x20{\x20stroke:var(--bad);\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.ln-err\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{\x20stroke:var(--err);\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.ln-stale\x20\x20\x20\x20\x20\x20\x20\x20{\x20stroke:var(--okdb-app-fg-subtle);\x20opacity:0.5;\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.ln-disabled\x20\x20\x20\x20\x20{\x20stroke:var(--dis);\x20stroke-dasharray:6\x204;\x20opacity:0.45;\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.ln-dim\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{\x20opacity:0.08;\x20filter:none\x20!important;\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.ln-hover\x20\x20\x20\x20\x20\x20\x20\x20{\x20stroke-width:3.0;\x20opacity:1;\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.ln-selected\x20\x20\x20\x20\x20{\x20stroke:var(--accent)\x20!important;\x20stroke-width:5.2\x20!important;\x20opacity:1\x20!important;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20filter:drop-shadow(0\x200\x204px\x20rgba(77,163,255,0.9))\x20drop-shadow(0\x200\x2010px\x20rgba(77,163,255,0.35));\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20.nd\x20circle\x20{\x20fill:var(--bg);\x20stroke:var(--border);\x20stroke-width:2;\x20transition:stroke\x200.14s,stroke-width\x200.14s,filter\x200.14s,opacity\x200.14s;\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.nd\x20text\x20\x20\x20{\x20fill:var(--muted);\x20font-size:11px;\x20pointer-events:none;\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.nd-hover\x20\x20circle\x20{\x20stroke:rgba(77,163,255,0.8);\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.nd-sel\x20\x20\x20\x20circle\x20{\x20stroke:var(--accent);\x20stroke-width:3.4;\x20filter:drop-shadow(0\x200\x204px\x20rgba(77,163,255,0.75))\x20drop-shadow(0\x200\x2010px\x20rgba(77,163,255,0.25));\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.nd-sel2\x20\x20\x20circle\x20{\x20stroke:rgba(77,163,255,0.75);\x20stroke-width:3.0;\x20filter:drop-shadow(0\x200\x206px\x20rgba(77,163,255,0.25));\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.nd-dim\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{\x20opacity:0.22;\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.nd-err\x20\x20\x20\x20circle\x20\x20{\x20stroke:var(--err);\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.nd-stale\x20\x20circle\x20\x20{\x20stroke:var(--okdb-app-fg-subtle);\x20stroke-width:2;\x20opacity:0.7;\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.nd-behind\x20circle\x20\x20{\x20stroke:var(--okdb-info);\x20stroke-width:2.5;\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.nd-lag\x20\x20\x20\x20circle\x20\x20{\x20stroke:var(--warn);\x20stroke-width:3;\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.nd-critical\x20circle\x20{\x20stroke:var(--bad);\x20stroke-width:3.5;\x20filter:drop-shadow(0\x200\x206px\x20rgba(255,77,77,0.5));\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.fresh-now\x20\x20\x20{\x20color:var(--ok);\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.fresh-ok\x20\x20\x20\x20{\x20color:var(--okdb-info);\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.fresh-stale\x20{\x20color:var(--warn);\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20.spinner\x20{\x20display:inline-block;\x20width:12px;\x20height:12px;\x20border:2px\x20solid\x20rgba(77,163,255,0.2);\x20border-top:2px\x20solid\x20var(--accent);\x20border-radius:50%;\x20animation:spin\x200.8s\x20linear\x20infinite;\x20flex-shrink:0;\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20@keyframes\x20dash\x20{\x20to\x20{\x20stroke-dashoffset:-14;\x20}\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20@keyframes\x20spin\x20\x20{\x20to\x20{\x20transform:rotate(360deg);\x20}\x20}</style><g\x20class=\x22topo-g\x22><for\x20:=\x22links\x22\x20let:l><if\x20:=\x22!isActiveLink(l)\x22><g><path\x20:d=\x22linkPath(l)\x22\x20stroke=\x22transparent\x22\x20stroke-width=\x2214\x22\x20fill=\x22none\x22\x20:style=\x22_sPointerEventsFor(l)\x22\x20@mouseenter=\x22isLinkInteractable(l)?(hoveredLinkKey=l.key):null\x22\x20@mouseleave=\x22hoveredLinkKey=null\x22\x20@click=\x22pickLink(l.key)\x22/><path\x20:d=\x22linkPath(l)\x22\x20:class=\x22linkClass(l)\x22\x20:marker-end=\x22linkMarker(l)\x22\x20:marker-start=\x22linkStartMarker(l)\x22><title>{{l.lo.slice(0,8)}}\x20↔\x20{{l.hi.slice(0,8)}}\x20direction:\x20{{l.link\x20?\x20l.link.direction\x20:\x20\x27mesh\x27}}\x20enabled:\x20{{l.link\x20?\x20l.link.enabled\x20:\x20true}}\x20envs:\x20{{l.link\x20&&\x20l.link.envs\x20?\x20l.link.envs.join(\x27,\x20\x27)\x20:\x20\x27all\x27}}</title></path></g></if></for><for\x20:=\x22nodeList\x22\x20let:n><g\x20:id=\x22\x27nd-\x27+n.id\x22\x20:transform=\x22\x27translate(\x27+n.px+\x27,\x27+n.py+\x27)\x27\x22\x20:class=\x22nodeClass(n)\x22\x20@mouseenter=\x22hoveredNodeId=n.id\x22\x20@mouseleave=\x22hoveredNodeId=null\x22\x20@click=\x22pickNode(n.id)\x22><circle\x20:r=\x22nodeRadius()\x22/><text\x20text-anchor=\x22middle\x22\x20dy=\x224\x22>{{n.id.slice(0,6)}}</text><title>{{n.id}}\x20clock:\x20{{n.clock\x20??\x20\x27--\x27}}\x20addr:\x20{{n.address\x20??\x20\x27—\x27}}</title></g></for><for\x20:=\x22links\x22\x20let:l><if\x20:=\x22isActiveLink(l)\x22><g><path\x20:d=\x22linkPath(l)\x22\x20stroke=\x22transparent\x22\x20stroke-width=\x2216\x22\x20fill=\x22none\x22\x20:style=\x22_sPointerEventsFor(l)\x22\x20@mouseenter=\x22isLinkInteractable(l)?(hoveredLinkKey=l.key):null\x22\x20@mouseleave=\x22hoveredLinkKey=null\x22\x20@click=\x22pickLink(l.key)\x22/><path\x20:d=\x22linkPath(l)\x22\x20:class=\x22linkClass(l)\x22\x20:marker-end=\x22linkMarker(l)\x22\x20:marker-start=\x22linkStartMarker(l)\x22/></g></if></for></g></svg><if\x20:=\x22selection\x20||\x20editingLink\x22><div\x20style=\x22position:absolute;top:18px;right:18px;width:320px;background:var(--panel);border:1px\x20solid\x20var(--border);border-radius:12px;padding:16px;box-shadow:0\x2016px\x2034px\x20rgba(0,0,0,0.45);border-left:4px\x20solid\x20var(--accent);\x22><div\x20style=\x22display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;\x22><span\x20style=\x22font-size:10px;background:var(--bg);padding:3px\x207px;border-radius:6px;color:var(--accent);letter-spacing:0.6px;\x22>{{editingLink\x20?\x20\x27LINK_CONFIG\x27\x20:\x20selection.type.toUpperCase()}}\x20</span><span\x20@click=\x22editingLink\x20?\x20closeLinkEditor()\x20:\x20deselect()\x22\x20style=\x22cursor:pointer;opacity:0.7;font-size:16px;line-height:12px;\x22>×</span></div><if\x20:=\x22!editingLink\x20&&\x20selection\x20&&\x20selection.type\x20===\x20\x27node\x27\x22><div\x20style=\x22font-size:13px;\x22><div\x20style=\x22display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px;\x22><div\x20style=\x22color:var(--text);font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;\x22>{{selection.id}}</div><button\x20@click=\x22copy(selection.id)\x22\x20style=\x22border:1px\x20solid\x20var(--border);background:var(--panel2);color:var(--muted);font-size:11px;padding:4px\x208px;border-radius:8px;cursor:pointer;\x22>copy</button></div><div\x20style=\x22display:grid;grid-template-columns:92px\x201fr;row-gap:8px;column-gap:10px;\x22><div\x20style=\x22opacity:0.65;\x22>Clock</div><div>{{selection.data.clock??\x27--\x27}}</div><div\x20style=\x22opacity:0.65;\x22>Addr</div><div\x20style=\x22font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;\x22>{{selection.data.address||\x27N/A\x27}}</div><div\x20style=\x22opacity:0.65;\x22>Status</div><div\x20:style=\x22selection.data.error?\x27color:var(--err)\x27:\x27color:var(--ok)\x27\x22>{{selection.data.error?(\x27ERR:\x20\x27+selection.data.error):\x27OK\x27}}</div></div><div\x20style=\x22margin-top:12px;border-top:1px\x20solid\x20var(--border2);padding-top:10px;\x22><div\x20style=\x22font-size:11px;opacity:0.7;margin-bottom:6px;\x22>Tags</div><div\x20style=\x22display:flex;flex-wrap:wrap;gap:6px;\x22><if\x20:=\x22!(selection.data.tags\x20||\x20[]).length\x22><span\x20style=\x22font-size:11px;opacity:0.45;\x22>No\x20tags</span></if><for\x20:=\x22selection.data.tags\x20||\x20[]\x22\x20let:tag><span\x20style=\x22font-size:10px;padding:2px\x208px;border-radius:999px;border:1px\x20solid\x20var(--okdb-accent-soft-border);color:var(--okdb-accent);background:var(--okdb-accent-soft-bg);\x22>{{tag}}</span></for></div></div><div\x20style=\x22margin-top:12px;border-top:1px\x20solid\x20var(--border2);padding-top:10px;\x22><div\x20style=\x22font-size:11px;opacity:0.7;margin-bottom:6px;\x22>Available\x20envs</div><div\x20style=\x22font-size:11px;color:var(--muted);line-height:1.5;word-break:break-word;\x22>{{(selection.data.availableEnvs\x20||\x20[]).join(\x27,\x20\x27)\x20||\x20\x27—\x27}}</div></div><div\x20style=\x22margin-top:12px;border-top:1px\x20solid\x20var(--border2);padding-top:10px;\x22><div\x20style=\x22font-size:11px;opacity:0.7;margin-bottom:6px;\x22>Outbound\x20peers</div><div\x20style=\x22display:flex;flex-wrap:wrap;gap:6px;\x22><for\x20:=\x22Object.keys(selection.data.peers||{})\x22\x20let:pid><span\x20style=\x22font-size:11px;padding:3px\x207px;border:1px\x20solid\x20var(--border);background:var(--panel2);border-radius:999px;color:var(--muted);\x22>{{pid.slice(0,8)}}</span></for></div></div><div\x20style=\x22margin-top:14px;\x22><button\x20@click=\x22reconcileNode(selection.id)\x22\x20:disabled=\x22reconciling\x22\x20style=\x22width:100%;box-sizing:border-box;padding:6px\x2010px;border-radius:8px;border:1px\x20solid\x20var(--border);background:var(--panel2);color:var(--accent);cursor:pointer;font-size:12px;\x22><span\x20style=\x22display:flex;align-items:center;justify-content:center;gap:6px;\x22><span\x20:style=\x22reconcilingTarget===selection.id?\x27display:inline-block\x27:\x27display:none\x27\x22\x20class=\x22spinner\x22/>\x20{{reconcilingTarget===selection.id?\x27RECONCILING\x27:\x27RECONCILE\x20NODE\x27}}</span></button></div></div></if><if\x20:=\x22!editingLink\x20&&\x20selection\x20&&\x20selection.type\x20===\x20\x27link\x27\x22><div\x20style=\x22font-size:13px;\x22><div\x20style=\x22margin-bottom:12px;\x22><div\x20style=\x22display:flex;align-items:center;gap:8px;margin-bottom:6px;\x22><span\x20style=\x22font-family:monospace;font-size:12px;font-weight:700;\x22>{{selection.data.lo.slice(0,8)}}</span>\x20<span\x20style=\x22font-size:16px;color:var(--accent);\x22>{{selection.data.link\x20?\x20(selection.data.link.direction\x20===\x20\x27lo_to_hi\x27\x20?\x20\x27→\x27\x20:\x20selection.data.link.direction\x20===\x20\x27hi_to_lo\x27\x20?\x20\x27←\x27\x20:\x20\x27⇄\x27)\x20:\x20\x27⇄\x27}}\x20</span><span\x20style=\x22font-family:monospace;font-size:12px;font-weight:700;\x22>{{selection.data.hi.slice(0,8)}}</span><if\x20:=\x22selection.data.link\x22><span\x20style=\x22font-size:11px;margin-left:4px;\x22\x20:style=\x22_sEnabledBadge(selection.data.link.enabled)\x22>{{selection.data.link.enabled\x20?\x20\x27●\x20on\x27\x20:\x20\x27○\x20off\x27}}</span></if></div><if\x20:=\x22selection.data.link\x20&&\x20selection.data.link.envs\x22><div\x20style=\x22font-size:11px;opacity:0.45;\x22>envs:\x20{{selection.data.link.envs.join(\x27,\x20\x27)}}</div></if><if\x20:=\x22!selection.data.link\x22><div\x20style=\x22font-size:11px;opacity:0.45;\x22>no\x20link\x20config\x20—\x20mesh\x20(legacy)</div></if></div><div\x20style=\x22border:1px\x20solid\x20var(--border2);border-radius:8px;overflow:hidden;margin-bottom:12px;font-size:11px;\x22><if\x20:=\x22!selection.data.link\x20||\x20selection.data.link.direction\x20===\x20\x27both\x27\x20||\x20selection.data.link.direction\x20===\x20\x27lo_to_hi\x27\x22><div\x20style=\x22padding:9px\x2011px;border-bottom:1px\x20solid\x20var(--bg);\x22><div\x20style=\x22display:flex;align-items:center;gap:6px;margin-bottom:6px;\x22><span\x20style=\x22font-family:monospace;opacity:0.7;\x22>{{selection.data.lo.slice(0,6)}}</span>\x20<span\x20style=\x22color:var(--accent);\x22>→</span>\x20<span\x20style=\x22font-family:monospace;opacity:0.7;\x22>{{selection.data.hi.slice(0,6)}}</span>\x20<span\x20style=\x22opacity:0.4;\x22>(hi\x20receives\x20from\x20lo)</span></div><div\x20style=\x22display:grid;grid-template-columns:70px\x201fr;row-gap:4px;gap:6px;opacity:0.85;\x22><span\x20style=\x22opacity:0.5;\x22>status</span>\x20<span>{{selection.data.hi_pull\x20?\x20selection.data.hi_pull.status\x20:\x20\x27—\x27}}</span>\x20<span\x20style=\x22opacity:0.5;\x22>backlog</span>\x20<span>{{selection.data.hi_pull\x20?\x20selection.data.hi_pull.backlog\x20:\x20\x27—\x27}}</span>\x20<span\x20style=\x22opacity:0.5;\x22>last\x20seen</span>\x20<span>{{selection.data.hi_pull\x20&&\x20selection.data.hi_pull.last_seen\x20?\x20(Math.round((now\x20-\x20selection.data.hi_pull.last_seen)/1000)\x20+\x20\x27s\x20ago\x27)\x20:\x20\x27—\x27}}</span><if\x20:=\x22selection.data.hi_pull\x20&&\x20selection.data.hi_pull.last_error\x22><span\x20style=\x22opacity:0.5;\x22>error</span>\x20<span\x20style=\x22color:var(--err);\x22>{{selection.data.hi_pull.last_error}}</span></if></div></div></if><if\x20:=\x22!selection.data.link\x20||\x20selection.data.link.direction\x20===\x20\x27both\x27\x20||\x20selection.data.link.direction\x20===\x20\x27hi_to_lo\x27\x22><div\x20style=\x22padding:9px\x2011px;\x22><div\x20style=\x22display:flex;align-items:center;gap:6px;margin-bottom:6px;\x22><span\x20style=\x22font-family:monospace;opacity:0.7;\x22>{{selection.data.hi.slice(0,6)}}</span>\x20<span\x20style=\x22color:var(--accent);\x22>→</span>\x20<span\x20style=\x22font-family:monospace;opacity:0.7;\x22>{{selection.data.lo.slice(0,6)}}</span>\x20<span\x20style=\x22opacity:0.4;\x22>(lo\x20receives\x20from\x20hi)</span></div><div\x20style=\x22display:grid;grid-template-columns:70px\x201fr;row-gap:4px;gap:6px;opacity:0.85;\x22><span\x20style=\x22opacity:0.5;\x22>status</span>\x20<span>{{selection.data.lo_pull\x20?\x20selection.data.lo_pull.status\x20:\x20\x27—\x27}}</span>\x20<span\x20style=\x22opacity:0.5;\x22>backlog</span>\x20<span>{{selection.data.lo_pull\x20?\x20selection.data.lo_pull.backlog\x20:\x20\x27—\x27}}</span>\x20<span\x20style=\x22opacity:0.5;\x22>last\x20seen</span>\x20<span>{{selection.data.lo_pull\x20&&\x20selection.data.lo_pull.last_seen\x20?\x20(Math.round((now\x20-\x20selection.data.lo_pull.last_seen)/1000)\x20+\x20\x27s\x20ago\x27)\x20:\x20\x27—\x27}}</span><if\x20:=\x22selection.data.lo_pull\x20&&\x20selection.data.lo_pull.last_error\x22><span\x20style=\x22opacity:0.5;\x22>error</span>\x20<span\x20style=\x22color:var(--err);\x22>{{selection.data.lo_pull.last_error}}</span></if></div></div></if></div><div\x20style=\x22display:flex;flex-direction:column;gap:7px;\x22><button\x20@click=\x22reconcileLink(selection.data)\x22\x20:disabled=\x22reconciling\x22\x20style=\x22width:100%;box-sizing:border-box;padding:7px;border-radius:8px;border:1px\x20solid\x20var(--border);background:var(--panel2);color:var(--accent);cursor:pointer;font-size:12px;\x22><span\x20style=\x22display:flex;align-items:center;justify-content:center;gap:6px;\x22><span\x20:style=\x22reconcilingTarget===selection.data.key?\x27display:inline-block\x27:\x27display:none\x27\x22\x20class=\x22spinner\x22/>\x20{{reconcilingTarget===selection.data.key\x20?\x20\x27Reconciling…\x27\x20:\x20\x27Reconcile\x27}}</span></button>\x20<button\x20@click=\x22openLinkEditor(selection.data)\x22\x20style=\x22width:100%;box-sizing:border-box;padding:7px;border-radius:8px;border:1px\x20solid\x20var(--border);background:var(--panel2);color:var(--muted);cursor:pointer;font-size:12px;\x22>✎\x20Configure\x20Link</button></div></div></if><if\x20:=\x22editingLink\x22><div\x20style=\x22font-size:13px;\x22><div\x20style=\x22margin-bottom:14px;\x22><div\x20style=\x22font-family:monospace;font-size:11px;opacity:0.5;margin-bottom:2px;\x22>link\x20id</div><div\x20style=\x22font-family:monospace;font-size:11px;\x22>{{editingLink.key}}</div></div><div\x20style=\x22margin-bottom:14px;\x22><div\x20style=\x22font-size:11px;opacity:0.5;margin-bottom:7px;\x22>Direction</div><div\x20style=\x22display:flex;flex-direction:column;gap:5px;\x22><for\x20:=\x22dirButtons\x22\x20let:btn><button\x20@click=\x22linkEditDir\x20=\x20btn.canon\x22\x20:style=\x22btn.style\x22\x20style=\x22width:100%;box-sizing:border-box;padding:7px\x2010px;border-radius:7px;border:1px\x20solid\x20var(--border);background:var(--panel2);color:var(--muted);cursor:pointer;font-size:12px;font-family:monospace;text-align:left;\x22>{{btn.label}}</button></for></div></div><if\x20:=\x22availableEnvs.length\x22><div\x20style=\x22margin-bottom:14px;\x22><div\x20style=\x22font-size:11px;opacity:0.5;margin-bottom:7px;\x22>Data\x20envs\x20—\x20{{linkEditEnvs\x20===\x20null\x20?\x20\x27all\x27\x20:\x20linkEditEnvs.length\x20+\x20\x27\x20selected\x27}}</div><div\x20style=\x22display:flex;flex-wrap:wrap;gap:5px;\x22><for\x20:=\x22envButtons\x22\x20let:btn><button\x20@click=\x22toggleLinkEnv(btn.name)\x22\x20:style=\x22btn.style\x22\x20style=\x22padding:4px\x2010px;border-radius:6px;border:1px\x20solid\x20var(--border);background:var(--panel2);color:var(--muted);cursor:pointer;font-size:11px;\x22>{{btn.name}}</button></for></div></div></if><div\x20style=\x22margin-bottom:16px;display:flex;align-items:center;gap:10px;\x22><span\x20style=\x22font-size:11px;opacity:0.5;\x22>Enabled</span>\x20<button\x20@click=\x22linkEditEnabled=!linkEditEnabled\x22\x20:style=\x22_sEnabledBtn(linkEditEnabled)\x22\x20style=\x22padding:4px\x2012px;border-radius:6px;border:1px\x20solid;background:var(--panel2);cursor:pointer;font-size:11px;\x22>{{linkEditEnabled\x20?\x20\x27●\x20ON\x27\x20:\x20\x27○\x20OFF\x27}}</button></div><div\x20style=\x22display:flex;gap:8px;\x22><button\x20@click=\x22saveLink()\x22\x20:disabled=\x22savingLink\x22\x20style=\x22flex:1;padding:8px;border-radius:8px;border:1px\x20solid\x20var(--accent);background:rgba(77,163,255,0.1);color:var(--accent);cursor:pointer;font-size:12px;\x22><span\x20style=\x22display:flex;align-items:center;justify-content:center;gap:5px;\x22><span\x20:style=\x22savingLink?\x27display:inline-block\x27:\x27display:none\x27\x22\x20class=\x22spinner\x22/>\x20Save</span></button>\x20<button\x20@click=\x22deleteLink()\x22\x20:disabled=\x22savingLink\x22\x20style=\x22padding:8px\x2014px;border-radius:8px;border:1px\x20solid\x20#3d1a1a;background:rgba(255,59,48,0.08);color:var(--err);cursor:pointer;font-size:12px;\x22>Remove</button></div></div></if></div></if></div><div\x20style=\x22padding:9px\x2014px;background:var(--bg);border-top:1px\x20solid\x20var(--border2);display:flex;gap:16px;font-size:11px;align-items:center;flex-wrap:wrap;opacity:0.9;\x22><span><span\x20style=\x22color:var(--ok);\x22>●</span>\x20synced</span>\x20<span><span\x20style=\x22color:var(--okdb-info);\x22>●</span>\x20behind</span>\x20<span><span\x20style=\x22color:var(--rec);\x22>●</span>\x20reconciling</span>\x20<span><span\x20style=\x22color:var(--warn);\x22>●</span>\x20lag</span>\x20<span><span\x20style=\x22color:var(--err);\x22>●</span>\x20error</span>\x20<span><span\x20style=\x22color:var(--dis);\x22>●</span>\x20disabled</span>\x20<span\x20style=\x22opacity:0.4;\x22>⇄\x20both\x20→\x20send\x20←\x20receive\x20--\x20disabled\x20(click\x20to\x20inspect)</span></div></div>'];_0x15a2=function(){return _0x2ceb39;};return _0x15a2();}const _0x4a26d6=_0x3111;function _0x3111(_0x14d4a3,_0x106efd){_0x14d4a3=_0x14d4a3-0xba;const _0x15a23b=_0x15a2();let _0x3111ba=_0x15a23b[_0x14d4a3];return _0x3111ba;}(function(_0x504811,_0x1e5e47){const _0xc1ed=_0x3111,_0x21d417=_0x504811();while(!![]){try{const _0x294e9d=-parseInt(_0xc1ed(0x152))/0x1+-parseInt(_0xc1ed(0xee))/0x2+-parseInt(_0xc1ed(0xcd))/0x3+-parseInt(_0xc1ed(0x13c))/0x4+-parseInt(_0xc1ed(0x13f))/0x5+parseInt(_0xc1ed(0xc9))/0x6+parseInt(_0xc1ed(0x142))/0x7;if(_0x294e9d===_0x1e5e47)break;else _0x21d417['push'](_0x21d417['shift']());}catch(_0x32f5a0){_0x21d417['push'](_0x21d417['shift']());}}}(_0x15a2,0xe9a4b));export default{'tag':'sync-topology','unwrap':!0x0,'context':_0x18a441=>({'w':0x384,'h':0x1f4,'layout':{'cx':0x0,'cy':0x0,'r':0x0,'pos':{}},'_ro':null,'_tick':null,'now':Date[_0x4a26d6(0x117)](),'selectedNodeId':null,'selectedLinkKey':null,'hoveredLinkKey':null,'hoveredNodeId':null,'lastUpdatedAt':null,'autoRefresh':!0x0,'autoRefreshIntervalMs':0x1388,'_autoRefreshTimer':null,'loadingTopology':!0x1,'editingLink':null,'linkEditDir':_0x4a26d6(0x11b),'linkEditEnvs':null,'linkEditEnabled':!0x0,'savingLink':!0x1,'panX':0x0,'panY':0x0,'zoom':0x1,'_drag':null,'_customPos':{},'_svgEl':null,'_groupEl':null,'_applyTransform'(){const _0x542513=_0x4a26d6;this['_groupEl']||(this[_0x542513(0x156)]=this[_0x542513(0x169)]?.[_0x542513(0xbd)](_0x542513(0x143))),this[_0x542513(0x156)]&&this[_0x542513(0x156)][_0x542513(0xe7)](_0x542513(0x145),_0x542513(0x11c)+this[_0x542513(0xd2)]+','+this['panY']+_0x542513(0x151)+this['zoom']+')');},'_px2vb'(){const _0x17e13e=_0x4a26d6;if(!this['_svgEl'])return{'sx':0x1,'sy':0x1};const _0x2a8ecf=this[_0x17e13e(0x169)][_0x17e13e(0x16d)]();return{'sx':this['w']/(_0x2a8ecf[_0x17e13e(0xbf)]||0x1),'sy':this['h']/(_0x2a8ecf[_0x17e13e(0x13e)]||0x1)};},'_onWheel'(_0x2cfbd5){const _0x254d32=_0x4a26d6;if(!this['_svgEl'])return;const _0xc19cd8=this[_0x254d32(0x169)][_0x254d32(0x16d)](),{sx:_0x3d5760,sy:_0x25af5d}=this['_px2vb'](),_0x38e12d=(_0x2cfbd5[_0x254d32(0xc4)]-_0xc19cd8['left'])*_0x3d5760,_0x341ba7=(_0x2cfbd5['clientY']-_0xc19cd8[_0x254d32(0xd1)])*_0x25af5d,_0x452a1c=_0x2cfbd5[_0x254d32(0xc2)]<0x0?1.12:0x1/1.12,_0x185291=Math[_0x254d32(0xe2)](0.1,Math[_0x254d32(0x10e)](0x8,this[_0x254d32(0xff)]*_0x452a1c)),_0x27dab7=_0x185291/this['zoom'];this[_0x254d32(0xd2)]=_0x38e12d+(this[_0x254d32(0xd2)]-_0x38e12d)*_0x27dab7,this[_0x254d32(0x123)]=_0x341ba7+(this[_0x254d32(0x123)]-_0x341ba7)*_0x27dab7,this[_0x254d32(0xff)]=_0x185291,this[_0x254d32(0x166)]();},'_onSvgMousedown'(_0x18386e){const _0xbc9fdc=_0x4a26d6;if(0x0!==_0x18386e['button'])return;_0x18386e[_0xbc9fdc(0x155)]();let _0x1333a2=null,_0x3677eb=_0x18386e['target'];for(;_0x3677eb&&_0x3677eb!==this[_0xbc9fdc(0x169)];){if(_0x3677eb['id']&&_0x3677eb['id'][_0xbc9fdc(0x12a)]('nd-')){_0x1333a2=_0x3677eb['id'][_0xbc9fdc(0x14a)](0x3);break;}_0x3677eb=_0x3677eb[_0xbc9fdc(0xdb)];}if(_0x1333a2){const _0x4d67aa=this[_0xbc9fdc(0x169)][_0xbc9fdc(0x16d)](),{sx:_0x1300a4,sy:_0x21e68b}=this[_0xbc9fdc(0x182)](),_0x1bcc44=(_0x18386e[_0xbc9fdc(0xc4)]-_0x4d67aa[_0xbc9fdc(0xf9)])*_0x1300a4,_0x42c0cc=(_0x18386e[_0xbc9fdc(0x16e)]-_0x4d67aa[_0xbc9fdc(0xd1)])*_0x21e68b,_0x42cf3b=(_0x1bcc44-this['panX'])/this[_0xbc9fdc(0xff)],_0x580d84=(_0x42c0cc-this[_0xbc9fdc(0x123)])/this[_0xbc9fdc(0xff)],_0x170ef3=this[_0xbc9fdc(0x148)](_0x1333a2)||{'x':_0x42cf3b,'y':_0x580d84};this[_0xbc9fdc(0x15f)]={'type':_0xbc9fdc(0xf3),'id':_0x1333a2,'ox':_0x42cf3b-_0x170ef3['x'],'oy':_0x580d84-_0x170ef3['y']};}else this['_drag']={'type':_0xbc9fdc(0x181),'sx':_0x18386e['clientX'],'sy':_0x18386e[_0xbc9fdc(0x16e)],'px':this[_0xbc9fdc(0xd2)],'py':this['panY']};this[_0xbc9fdc(0x169)]&&(this[_0xbc9fdc(0x169)][_0xbc9fdc(0x139)][_0xbc9fdc(0xdd)]=_0xbc9fdc(0xd6));},'_onMousemove'(_0x26f495){const _0x1e1c54=_0x4a26d6;if(!this[_0x1e1c54(0x15f)])return;const {sx:_0x4838d7,sy:_0x56089d}=this[_0x1e1c54(0x182)]();if(_0x1e1c54(0x181)===this['_drag'][_0x1e1c54(0x100)])this[_0x1e1c54(0xd2)]=this['_drag']['px']+(_0x26f495[_0x1e1c54(0xc4)]-this['_drag']['sx'])*_0x4838d7,this[_0x1e1c54(0x123)]=this[_0x1e1c54(0x15f)]['py']+(_0x26f495[_0x1e1c54(0x16e)]-this[_0x1e1c54(0x15f)]['sy'])*_0x56089d,this[_0x1e1c54(0x166)]();else{const _0xe18dbe=this[_0x1e1c54(0x169)][_0x1e1c54(0x16d)](),_0x424a82=(_0x26f495[_0x1e1c54(0xc4)]-_0xe18dbe[_0x1e1c54(0xf9)])*_0x4838d7,_0x13fa24=(_0x26f495[_0x1e1c54(0x16e)]-_0xe18dbe[_0x1e1c54(0xd1)])*_0x56089d,_0x440d4b=(_0x424a82-this[_0x1e1c54(0xd2)])/this[_0x1e1c54(0xff)],_0x2a3808=(_0x13fa24-this[_0x1e1c54(0x123)])/this[_0x1e1c54(0xff)];this['_customPos']={...this['_customPos'],[this[_0x1e1c54(0x15f)]['id']]:{'x':_0x440d4b-this[_0x1e1c54(0x15f)]['ox'],'y':_0x2a3808-this[_0x1e1c54(0x15f)]['oy']}},this[_0x1e1c54(0x160)]();}},'_onMouseup'(){const _0x547cfd=_0x4a26d6;this[_0x547cfd(0x15f)]=null,this['_svgEl']&&(this['_svgEl']['style'][_0x547cfd(0xdd)]=_0x547cfd(0xdc));},get 'externalSelectedNodeId'(){const _0x29de75=_0x4a26d6;return _0x18a441['$attr'][_0x29de75(0x17a)]??null;},get 'selfId'(){const _0x56ed28=_0x4a26d6;return _0x18a441[_0x56ed28(0x154)][_0x56ed28(0x184)];},get 'topology'(){const _0x2aa8b7=_0x4a26d6;return _0x18a441[_0x2aa8b7(0x154)]['topology']||{};},get 'availableEnvs'(){const _0x28c3cd=_0x4a26d6;return _0x18a441[_0x28c3cd(0x154)][_0x28c3cd(0x164)]||[];},get 'envButtons'(){const _0x4d2865=_0x4a26d6,_0x453e63=this[_0x4d2865(0xcb)];return(_0x18a441[_0x4d2865(0x154)][_0x4d2865(0x164)]||[])[_0x4d2865(0x161)](_0x26130c=>({'name':_0x26130c,'style':null===_0x453e63||Array['isArray'](_0x453e63)&&_0x453e63[_0x4d2865(0x10a)](_0x26130c)?_0x4d2865(0xe8):''}));},get 'dirButtons'(){const _0x26296a=_0x4a26d6,_0x49610e=this['editingLink'];if(!_0x49610e)return[];const _0x500e96=_0x49610e['lo']['slice'](0x0,0x6),_0xe769b5=_0x49610e['hi'][_0x26296a(0x14a)](0x0,0x6),_0x5365f2=this['linkEditDir'];return[{'canon':_0x26296a(0x11b),'label':_0x500e96+_0x26296a(0xea)+_0xe769b5},{'canon':_0x26296a(0xfd),'label':_0x500e96+_0x26296a(0x10f)+_0xe769b5},{'canon':_0x26296a(0x133),'label':_0xe769b5+'\x20→\x20'+_0x500e96}][_0x26296a(0x161)](_0x53843a=>({..._0x53843a,'style':_0x53843a[_0x26296a(0x16a)]===_0x5365f2?'border-color:var(--accent);color:var(--accent)':''}));},get 'nodes'(){const _0x7adb1d=_0x4a26d6;return Object[_0x7adb1d(0xd5)](this['topology']||{})[_0x7adb1d(0xd3)](_0x4b30de=>_0x4b30de&&null!=_0x4b30de['id'])[_0x7adb1d(0x16c)]((_0x572c48,_0x1bcf03)=>String(_0x572c48['id'])['localeCompare'](String(_0x1bcf03['id'])));},get 'nodeList'(){const _0x7a600a=_0x4a26d6;return this[_0x7a600a(0x157)][_0x7a600a(0x161)](_0x59aabb=>{const _0x4238ed=_0x7a600a,_0x1c4c71=this['_customPos'][_0x59aabb['id']]||this[_0x4238ed(0x174)]['pos']?.[_0x59aabb['id']]||{'x':this['w']/0x2,'y':this['h']/0x2};return{..._0x59aabb,'px':_0x1c4c71['x'],'py':_0x1c4c71['y']};});},get 'links'(){const _0x4d82db=_0x4a26d6,_0x259a07=this[_0x4d82db(0xf0)]||{},_0x472bb4=_0x259a07[this[_0x4d82db(0xfa)]]||Object[_0x4d82db(0xd5)](_0x259a07)['find'](_0x34bb7b=>null!=_0x34bb7b?.[_0x4d82db(0x172)]),_0xd00e00=_0x472bb4?.['links']||{},_0x2c0a53=new Map(),_0x4af9a6=(_0x565eae,_0x5058d1)=>({'status':_0x565eae['status']||_0x4d82db(0x11e),'backlog':Math['max'](0x0,Number(_0x5058d1??0x0)-Number(_0x565eae[_0x4d82db(0xba)]??0x0)),'last_seen':_0x565eae[_0x4d82db(0x137)]??null,'last_error':_0x565eae[_0x4d82db(0x14d)]??null});for(const _0xc3c1ff in _0x259a07){const _0x372dd9=_0x259a07[_0xc3c1ff];if(_0x372dd9?.['peers'])for(const _0x55c54b in _0x372dd9['peers']){const _0x509ecd=_0xc3c1ff<_0x55c54b?_0xc3c1ff:_0x55c54b,_0x832bad=_0xc3c1ff<_0x55c54b?_0x55c54b:_0xc3c1ff,_0x3c56a0=_0x509ecd+':'+_0x832bad;_0x2c0a53[_0x4d82db(0x11a)](_0x3c56a0)||_0x2c0a53[_0x4d82db(0x110)](_0x3c56a0,{'lo':_0x509ecd,'hi':_0x832bad,'lo_pull':null,'hi_pull':null});const _0x11e04c=_0x2c0a53[_0x4d82db(0x15d)](_0x3c56a0),_0x4f61f9=_0x372dd9['peers'][_0x55c54b];_0xc3c1ff===_0x509ecd?_0x11e04c[_0x4d82db(0x124)]=_0x4af9a6(_0x4f61f9,_0x259a07[_0x55c54b]?.[_0x4d82db(0xba)]):_0x11e04c[_0x4d82db(0x15c)]=_0x4af9a6(_0x4f61f9,_0x259a07[_0x55c54b]?.[_0x4d82db(0xba)]);}}for(const _0x47d608 in _0xd00e00)if(_0xd00e00[_0x47d608]&&!_0x2c0a53[_0x4d82db(0x11a)](_0x47d608)){const _0x5ddb2c=_0xd00e00[_0x47d608];_0x2c0a53[_0x4d82db(0x110)](_0x47d608,{'lo':_0x5ddb2c[_0x4d82db(0xd8)],'hi':_0x5ddb2c[_0x4d82db(0xf4)],'lo_pull':null,'hi_pull':null});}return[..._0x2c0a53['entries']()]['map'](([_0x2fc3e7,_0x5d1e03])=>({'key':_0x2fc3e7,'lo':_0x5d1e03['lo'],'hi':_0x5d1e03['hi'],'link':_0xd00e00[_0x2fc3e7]||null,'lo_pull':_0x5d1e03['lo_pull'],'hi_pull':_0x5d1e03['hi_pull']}));},'_linkConfig'(_0x1bc225){const _0x35531b=_0x4a26d6,_0x2ba74f=(this[_0x35531b(0xf0)]||{})[this[_0x35531b(0xfa)]];return _0x2ba74f?.[_0x35531b(0x172)]?.[_0x1bc225]||null;},'_linkHealth'(_0x41a7cd){const _0x1c517e=_0x4a26d6;if(_0x41a7cd['link']&&!_0x41a7cd[_0x1c517e(0xe5)][_0x1c517e(0x14f)])return'disabled';const _0x101242=_0x41a7cd[_0x1c517e(0xe5)]?.[_0x1c517e(0x109)]??'both',_0x497a1e=[];'both'!==_0x101242&&'lo_to_hi'!==_0x101242||_0x497a1e[_0x1c517e(0x125)](_0x41a7cd['hi_pull']),_0x1c517e(0x11b)!==_0x101242&&_0x1c517e(0x133)!==_0x101242||_0x497a1e[_0x1c517e(0x125)](_0x41a7cd[_0x1c517e(0x124)]);const _0x4cff43=['error',_0x1c517e(0x131),'lag-med',_0x1c517e(0xce),_0x1c517e(0x13d),'stale','ok'];let _0x3c58d1=_0x1c517e(0x141);for(const _0x2b6ea3 of _0x497a1e){const _0x2804d0=this[_0x1c517e(0xd7)](_0x2b6ea3);_0x4cff43[_0x1c517e(0x11f)](_0x2804d0)<_0x4cff43[_0x1c517e(0x11f)](_0x3c58d1)&&(_0x3c58d1=_0x2804d0);}return _0x3c58d1;},'_pullHealth'(_0x23a388){const _0x5d6b65=_0x4a26d6;if(!_0x23a388)return _0x5d6b65(0x141);if(_0x5d6b65(0x13d)===_0x23a388[_0x5d6b65(0x104)])return _0x5d6b65(0x13d);if(_0x23a388[_0x5d6b65(0x137)]){const _0x5009b8=this[_0x5d6b65(0x117)]-Number(_0x23a388[_0x5d6b65(0x137)]);return _0x23a388[_0x5d6b65(0x150)]>0x32?_0x5d6b65(0x131):_0x23a388[_0x5d6b65(0x150)]>0x5?'lag-med':_0x23a388[_0x5d6b65(0x150)]>0x0?'behind':_0x5009b8>0x7530?_0x5d6b65(0x141):'ok';}return _0x23a388[_0x5d6b65(0x14d)]?_0x5d6b65(0xc1):_0x5d6b65(0x141);},'_healthMarker':_0x341170=>'error'===_0x341170?'err':'reconciling'===_0x341170?_0x4a26d6(0x111):_0x4a26d6(0x131)===_0x341170?_0x4a26d6(0x131):_0x4a26d6(0x103)===_0x341170||_0x4a26d6(0xce)===_0x341170||_0x4a26d6(0x141)===_0x341170?'lag-med':'ok','_linkDirLabel'(_0x274e53){const _0x1276cc=_0x4a26d6;if(!_0x274e53['link'])return _0x274e53['lo'][_0x1276cc(0x14a)](0x0,0x6)+_0x1276cc(0xea)+_0x274e53['hi'][_0x1276cc(0x14a)](0x0,0x6)+_0x1276cc(0x170);if(!_0x274e53['link'][_0x1276cc(0x14f)])return'disabled';const _0x3c770c=_0x274e53['lo'][_0x1276cc(0x14a)](0x0,0x6),_0x2ff9f3=_0x274e53['hi'][_0x1276cc(0x14a)](0x0,0x6);switch(_0x274e53[_0x1276cc(0xe5)][_0x1276cc(0x109)]){case _0x1276cc(0x11b):return _0x3c770c+'\x20⇄\x20'+_0x2ff9f3;case _0x1276cc(0xfd):return _0x3c770c+_0x1276cc(0x10f)+_0x2ff9f3;case _0x1276cc(0x133):return _0x2ff9f3+_0x1276cc(0x10f)+_0x3c770c;default:return _0x274e53[_0x1276cc(0xe5)]['direction'];}},get 'selection'(){const _0x5a125d=_0x4a26d6;if(this[_0x5a125d(0x12b)]){const _0x192bea=this['links'][_0x5a125d(0xbe)](_0x193b00=>_0x193b00['key']===this[_0x5a125d(0x12b)])||null;if(_0x192bea)return{'type':_0x5a125d(0xe5),'data':_0x192bea};}if(this['selectedNodeId']){const _0x5e875e=this[_0x5a125d(0xf0)][this[_0x5a125d(0x17e)]]||null;return _0x5e875e?{'type':_0x5a125d(0xf3),'id':this[_0x5a125d(0x17e)],'data':_0x5e875e}:null;}return null;},get 'freshnessLabel'(){const _0x1314c7=_0x4a26d6;if(!this['lastUpdatedAt'])return'never';const _0x144872=this['now']-this[_0x1314c7(0x15e)];return _0x144872<0x5dc?'now':_0x144872<0x1388?_0x1314c7(0x107):_0x144872<0x3a98?'recent':Math[_0x1314c7(0x126)](_0x144872/0x3e8)+'s\x20ago';},get 'freshnessClass'(){const _0x37e482=_0x4a26d6;if(!this[_0x37e482(0x15e)])return'fresh-stale';const _0x5472d7=this['now']-this[_0x37e482(0x15e)];return _0x5472d7<0x1388?_0x37e482(0xeb):_0x5472d7<0x3a98?_0x37e482(0x144):_0x37e482(0xf7);},'_recomputeLayout'(){const _0x5a7f11=_0x4a26d6,_0x2807a6=this[_0x5a7f11(0x157)],_0x397b2a=this['w']/0x2,_0x188dcd=this['h']/0x2,_0x515dd2=Math[_0x5a7f11(0xe2)](0x50,Math[_0x5a7f11(0x10e)](_0x397b2a,_0x188dcd)-this[_0x5a7f11(0x153)]()-0x3c),_0x23d73f={},_0x569cd2=_0x2807a6[_0x5a7f11(0x11d)]||0x1;_0x2807a6[_0x5a7f11(0xca)]((_0x95bf5e,_0x2e7f52)=>{const _0x17f6fa=_0x5a7f11;if(0x1===_0x569cd2)return void(_0x23d73f[_0x95bf5e['id']]={'x':_0x397b2a,'y':_0x188dcd});const _0x49691d=0x2===_0x569cd2?0x0===_0x2e7f52?Math['PI']:0x0:_0x2e7f52/_0x569cd2*Math['PI']*0x2-Math['PI']/0x2;_0x23d73f[_0x95bf5e['id']]={'x':_0x397b2a+_0x515dd2*Math[_0x17f6fa(0xfb)](_0x49691d),'y':_0x188dcd+_0x515dd2*Math[_0x17f6fa(0x113)](_0x49691d)};}),this[_0x5a7f11(0x174)]={'cx':_0x397b2a,'cy':_0x188dcd,'r':_0x515dd2,'pos':_0x23d73f};},'_measure'(){const _0x217e95=_0x4a26d6,_0x38caa7=(this[_0x217e95(0x169)]||_0x18a441[_0x217e95(0x17b)])[_0x217e95(0x16d)]();this['w']=Math[_0x217e95(0xe2)](0x12c,_0x38caa7[_0x217e95(0xbf)]||0x384),this['h']=Math[_0x217e95(0xe2)](0xc8,_0x38caa7[_0x217e95(0x13e)]||0x1f4),this[_0x217e95(0xf5)]();},'_effectivePos'(_0x5e9ac8){const _0x120efa=_0x4a26d6;return this[_0x120efa(0xfe)][_0x5e9ac8]||this['layout'][_0x120efa(0xe9)]?.[_0x5e9ac8]||null;},'fitView'(){const _0x4c3153=_0x4a26d6;this[_0x4c3153(0xe0)]();const _0x285268=this[_0x4c3153(0x157)];if(!_0x285268[_0x4c3153(0x11d)])return;let _0x2c37dd=0x1/0x0,_0x347a6b=0x1/0x0,_0x4891fd=-0x1/0x0,_0x2b9568=-0x1/0x0;for(const _0x54a6f9 of _0x285268){const _0x53b0bc=this[_0x4c3153(0x148)](_0x54a6f9['id']);_0x53b0bc&&(_0x2c37dd=Math['min'](_0x2c37dd,_0x53b0bc['x']),_0x347a6b=Math['min'](_0x347a6b,_0x53b0bc['y']),_0x4891fd=Math[_0x4c3153(0xe2)](_0x4891fd,_0x53b0bc['x']),_0x2b9568=Math[_0x4c3153(0xe2)](_0x2b9568,_0x53b0bc['y']));}if(!isFinite(_0x2c37dd))return;const _0x17967d=this[_0x4c3153(0x153)]()+0x3c;_0x2c37dd-=_0x17967d,_0x347a6b-=_0x17967d,_0x4891fd+=_0x17967d,_0x2b9568+=_0x17967d;const _0x5b457d=_0x4891fd-_0x2c37dd,_0x1d2fb6=_0x2b9568-_0x347a6b,_0x173e35=0.88*Math['min'](this['w']/_0x5b457d,this['h']/_0x1d2fb6,0x3);this[_0x4c3153(0xff)]=_0x173e35,this[_0x4c3153(0xd2)]=this['w']/0x2-(_0x2c37dd+_0x5b457d/0x2)*_0x173e35,this['panY']=this['h']/0x2-(_0x347a6b+_0x1d2fb6/0x2)*_0x173e35,this['_applyTransform']();},'resetLayout'(){const _0x4cb462=_0x4a26d6;this['_customPos']={};try{localStorage[_0x4cb462(0x173)](_0x4cb462(0x14b));}catch{}this['fitView']();},'_savePositions'(){const _0x11061e=_0x4a26d6;try{localStorage[_0x11061e(0xed)](_0x11061e(0x14b),JSON[_0x11061e(0x128)](this[_0x11061e(0xfe)]));}catch{}},'_loadPositions'(){const _0x10cced=_0x4a26d6;try{const _0x111bc5=localStorage[_0x10cced(0x159)](_0x10cced(0x14b));_0x111bc5&&(this['_customPos']=JSON[_0x10cced(0x167)](_0x111bc5));}catch{}},'nodeRadius':()=>0x16,'linkPath'(_0x3c79b7){const _0x4875cf=_0x4a26d6,_0x129848=this['_effectivePos'](_0x3c79b7['lo']),_0x3ba664=this[_0x4875cf(0x148)](_0x3c79b7['hi']);if(!_0x129848||!_0x3ba664)return'';const _0x2de9b5=_0x3ba664['x']-_0x129848['x'],_0x244871=_0x3ba664['y']-_0x129848['y'],_0x1bec33=Math[_0x4875cf(0x175)](_0x2de9b5*_0x2de9b5+_0x244871*_0x244871)||0x1,_0x1d453c=this['nodeRadius']();return'M\x20'+(_0x129848['x']+_0x2de9b5/_0x1bec33*_0x1d453c)+'\x20'+(_0x129848['y']+_0x244871/_0x1bec33*_0x1d453c)+_0x4875cf(0xcf)+(_0x3ba664['x']-_0x2de9b5/_0x1bec33*_0x1d453c)+'\x20'+(_0x3ba664['y']-_0x244871/_0x1bec33*_0x1d453c);},'reconciling':!0x1,'reconcilingTarget':null,async 'refreshTopology'(){const _0x554412=_0x4a26d6;if(!this['loadingTopology']){this[_0x554412(0x146)]=!0x0;try{const _0x2d6199=await fetch(_0x554412(0x163)),_0x18722a=await _0x2d6199[_0x554412(0x10b)](),_0x1148b7=_0x18722a['data']??_0x18722a;_0x18a441[_0x554412(0x154)][_0x554412(0xf0)]=_0x1148b7[_0x554412(0xf0)]??_0x1148b7,_0x18a441[_0x554412(0x154)][_0x554412(0x164)]=_0x1148b7[_0x554412(0xc8)]??[],this['lastUpdatedAt']=Date['now']();}catch(_0x41e1ca){console[_0x554412(0xc1)](_0x554412(0x179),_0x41e1ca);}finally{this[_0x554412(0x146)]=!0x1;}}},async 'reconcileAll'(){const _0xb48cf7=_0x4a26d6;if(!this[_0xb48cf7(0x13d)]){this[_0xb48cf7(0x13d)]=!0x0,this[_0xb48cf7(0xdf)]=_0xb48cf7(0x12c);try{await fetch('/api/sync/reconcile-all',{'method':_0xb48cf7(0xc0)}),await this[_0xb48cf7(0x101)]();}finally{this['reconciling']=!0x1,this[_0xb48cf7(0xdf)]=null;}}},async 'reconcileNode'(_0x3792b4){const _0x500803=_0x4a26d6;if(!this[_0x500803(0x13d)]){this[_0x500803(0x13d)]=!0x0,this[_0x500803(0xdf)]=_0x3792b4;try{_0x3792b4===this['selfId']?await fetch(_0x500803(0x15a),{'method':_0x500803(0xc0)}):await fetch(this[_0x500803(0xf0)][_0x3792b4]?.[_0x500803(0x17f)]+_0x500803(0x15a),{'method':_0x500803(0xc0)}),await this[_0x500803(0x101)]();}finally{this[_0x500803(0x13d)]=!0x1,this[_0x500803(0xdf)]=null;}}},async 'reconcileLink'(_0x4d6b0d){const _0x4ff58f=_0x4a26d6;if(this[_0x4ff58f(0x13d)])return;this['reconciling']=!0x0,this['reconcilingTarget']=_0x4d6b0d[_0x4ff58f(0x140)];const _0x914ce4=_0x4d6b0d['lo']===this[_0x4ff58f(0xfa)]?_0x4d6b0d['hi']:_0x4d6b0d['lo'];try{await fetch(_0x4ff58f(0xfc),{'method':_0x4ff58f(0xc0),'headers':{'Content-Type':_0x4ff58f(0x102)},'body':JSON[_0x4ff58f(0x128)]({'peer_id':_0x914ce4})}),await this[_0x4ff58f(0x101)]();}finally{this[_0x4ff58f(0x13d)]=!0x1,this['reconcilingTarget']=null;}},'openLinkEditor'(_0x242e36){const _0x11e22b=_0x4a26d6;this[_0x11e22b(0x149)]=_0x242e36,this['linkEditDir']=_0x242e36['link']?.[_0x11e22b(0x109)]??_0x11e22b(0x11b),this[_0x11e22b(0xcb)]=_0x242e36[_0x11e22b(0xe5)]?.[_0x11e22b(0xf2)]??null,this['linkEditEnabled']=!0x1!==_0x242e36[_0x11e22b(0xe5)]?.[_0x11e22b(0x14f)];},'closeLinkEditor'(){const _0x859f1f=_0x4a26d6;this[_0x859f1f(0x149)]=null;},'toggleLinkEnv'(_0x4fc8af){const _0x568084=_0x4a26d6;if(null===this[_0x568084(0xcb)])this[_0x568084(0xcb)]=this[_0x568084(0xc8)][_0x568084(0xd3)](_0x30ceaa=>_0x30ceaa!==_0x4fc8af);else{const _0x2146ea=new Set(this['linkEditEnvs']);_0x2146ea[_0x568084(0x11a)](_0x4fc8af)?_0x2146ea[_0x568084(0xd0)](_0x4fc8af):_0x2146ea['add'](_0x4fc8af);const _0x5c9043=[..._0x2146ea];this[_0x568084(0xcb)]=_0x5c9043['length']===this[_0x568084(0xc8)][_0x568084(0x11d)]?null:_0x5c9043;}},async 'saveLink'(){const _0x5b90c6=_0x4a26d6;if(!this[_0x5b90c6(0x149)]||this[_0x5b90c6(0x14c)])return;this['savingLink']=!0x0;const _0x2f8a41=this[_0x5b90c6(0x149)];try{const _0x57ef17=await fetch(_0x5b90c6(0x105)+_0x2f8a41['lo']+'/'+_0x2f8a41['hi'],{'method':'PUT','headers':{'Content-Type':_0x5b90c6(0x102)},'body':JSON['stringify']({'direction':this[_0x5b90c6(0xe4)],'envs':this[_0x5b90c6(0xcb)],'enabled':this[_0x5b90c6(0x176)]})});if(!_0x57ef17['ok']){const _0x5300e0=await _0x57ef17[_0x5b90c6(0x10b)]();throw new Error(_0x5300e0?.['error']?.['message']||_0x5b90c6(0x135));}this['closeLinkEditor'](),await this['refreshTopology']();}catch(_0x5acfc7){alert(_0x5acfc7[_0x5b90c6(0x16f)]);}finally{this[_0x5b90c6(0x14c)]=!0x1;}},async 'deleteLink'(){const _0x7e3014=_0x4a26d6;if(!this['editingLink']||this[_0x7e3014(0x14c)])return;if(!confirm(_0x7e3014(0x115)))return;this[_0x7e3014(0x14c)]=!0x0;const _0xa4479c=this['editingLink'];try{await fetch(_0x7e3014(0x105)+_0xa4479c['lo']+'/'+_0xa4479c['hi'],{'method':_0x7e3014(0xe6)}),this['closeLinkEditor'](),await this[_0x7e3014(0x101)]();}catch(_0x541433){alert(_0x541433[_0x7e3014(0x16f)]);}finally{this[_0x7e3014(0x14c)]=!0x1;}},'deselect'(){const _0x3b9d22=_0x4a26d6;this[_0x3b9d22(0x17e)]=null,this[_0x3b9d22(0x12b)]=null,this[_0x3b9d22(0xf6)]=null,this[_0x3b9d22(0x12d)]=null,this[_0x3b9d22(0x149)]=null,_0x18a441['$dispatch']?.(_0x3b9d22(0x129),null),_0x18a441[_0x3b9d22(0x119)]?.(_0x3b9d22(0x106),null);},'pickNode'(_0x47e756){const _0xc99840=_0x4a26d6;this['selectedNodeId']=_0x47e756,this[_0xc99840(0x12b)]=null,this[_0xc99840(0x149)]=null,_0x18a441[_0xc99840(0x119)]?.(_0xc99840(0x129),_0x47e756),_0x18a441[_0xc99840(0x119)]?.(_0xc99840(0x106),null);},'pickLink'(_0xb95794){const _0x5ba91d=_0x4a26d6,_0x202625=this[_0x5ba91d(0x172)]['find'](_0x4ae165=>_0x4ae165['key']===_0xb95794)||null;if(_0x202625&&(!this[_0x5ba91d(0x17e)]||_0x202625['lo']===this[_0x5ba91d(0x17e)]||_0x202625['hi']===this[_0x5ba91d(0x17e)])){if(this[_0x5ba91d(0x12b)]===_0xb95794)return this[_0x5ba91d(0x12b)]=null,this[_0x5ba91d(0x149)]=null,void _0x18a441[_0x5ba91d(0x119)]?.(_0x5ba91d(0x106),null);this['selectedLinkKey']=_0xb95794,this[_0x5ba91d(0x17e)]||(this[_0x5ba91d(0x17e)]=_0x202625['lo'],_0x18a441[_0x5ba91d(0x119)]?.(_0x5ba91d(0x129),this[_0x5ba91d(0x17e)])),_0x18a441[_0x5ba91d(0x119)]?.('selectLink',_0xb95794);}},async 'copy'(_0x14f665){const _0x1d7641=_0x4a26d6;try{await navigator[_0x1d7641(0xcc)][_0x1d7641(0xc7)](String(_0x14f665??''));}catch(_0x562234){}},'_sPointerEventsFor'(_0xab1ab8){const _0xb0395=_0x4a26d6;return this[_0xb0395(0x17e)]&&_0xab1ab8['lo']!==this[_0xb0395(0x17e)]&&_0xab1ab8['hi']!==this[_0xb0395(0x17e)]?_0xb0395(0x147):'pointer-events:stroke;cursor:pointer';},'_sEnabledBadge':_0x1a5f43=>_0x1a5f43?_0x4a26d6(0x158):_0x4a26d6(0x12e),'_sEnabledBtn':_0x1f6184=>_0x1f6184?'border-color:var(--ok);color:var(--ok)':_0x4a26d6(0xe3),'nodeHealth'(_0xe6c4e1){const _0x4ccb51=_0x4a26d6,_0x325882=this[_0x4ccb51(0x172)][_0x4ccb51(0xd3)](_0x75de49=>_0x75de49['lo']===_0xe6c4e1||_0x75de49['hi']===_0xe6c4e1);if(!_0x325882['length'])return'ok';const _0x389e52=_0x325882['map'](_0x1bd27c=>this['_linkHealth'](_0x1bd27c));return _0x389e52['includes'](_0x4ccb51(0xc1))?_0x4ccb51(0xc1):_0x389e52[_0x4ccb51(0x10a)](_0x4ccb51(0x131))?_0x4ccb51(0xbc):_0x389e52['includes'](_0x4ccb51(0x103))?'lagging':_0x389e52[_0x4ccb51(0x10a)](_0x4ccb51(0xce))?_0x4ccb51(0xce):_0x389e52['every'](_0x26ca13=>_0x4ccb51(0x141)===_0x26ca13||'disabled'===_0x26ca13)?_0x4ccb51(0x141):'ok';},'isLinkInteractable'(_0x1d5e92){const _0x50c41d=_0x4a26d6;return!this[_0x50c41d(0x17e)]||_0x1d5e92['lo']===this[_0x50c41d(0x17e)]||_0x1d5e92['hi']===this[_0x50c41d(0x17e)];},'isActiveLink'(_0x1f7855){const _0x35db12=_0x4a26d6;return this['selectedLinkKey']===_0x1f7855[_0x35db12(0x140)]||this[_0x35db12(0xf6)]===_0x1f7855[_0x35db12(0x140)]&&this['isLinkInteractable'](_0x1f7855);},'nodeClass'(_0x26e4b9){const _0x535bd1=_0x4a26d6;let _0x2790ff='nd';const _0x7e2ad5=this[_0x535bd1(0x17e)],_0x4160ab=this[_0x535bd1(0x12d)];if(_0x7e2ad5&&_0x7e2ad5!==_0x26e4b9['id']&&(_0x2790ff+=_0x535bd1(0x168)),_0x7e2ad5===_0x26e4b9['id']&&(_0x2790ff+='\x20nd-sel'),this[_0x535bd1(0x12b)]){const _0x5bc90a=this[_0x535bd1(0x172)]['find'](_0x4d4f5e=>_0x4d4f5e[_0x535bd1(0x140)]===this[_0x535bd1(0x12b)]);!_0x5bc90a||_0x5bc90a['lo']!==_0x26e4b9['id']&&_0x5bc90a['hi']!==_0x26e4b9['id']||_0x7e2ad5===_0x26e4b9['id']||(_0x2790ff+='\x20nd-sel2');}_0x7e2ad5||_0x4160ab!==_0x26e4b9['id']||(_0x2790ff+=_0x535bd1(0x127));const _0x1d5343=this[_0x535bd1(0xc3)](_0x26e4b9['id']);return _0x535bd1(0xc1)===_0x1d5343?_0x2790ff+='\x20nd-err':'critical'===_0x1d5343?_0x2790ff+=_0x535bd1(0x112):_0x535bd1(0x183)===_0x1d5343?_0x2790ff+=_0x535bd1(0x10d):_0x535bd1(0xce)===_0x1d5343?_0x2790ff+='\x20nd-behind':_0x535bd1(0x141)===_0x1d5343&&(_0x2790ff+=_0x535bd1(0x118)),_0x2790ff;},'linkClass'(_0x16f893){const _0x9c2f77=_0x4a26d6;let _0x23ee21='ln';const _0x23ea79=this[_0x9c2f77(0x12b)]===_0x16f893[_0x9c2f77(0x140)],_0x2c5f87=this[_0x9c2f77(0xf6)]===_0x16f893[_0x9c2f77(0x140)]&&this[_0x9c2f77(0x134)](_0x16f893);switch(this[_0x9c2f77(0x134)](_0x16f893)||_0x23ea79||(_0x23ee21+=_0x9c2f77(0xd9)),_0x23ea79?_0x23ee21+='\x20ln-selected':_0x2c5f87&&(_0x23ee21+='\x20ln-hover'),this['_linkHealth'](_0x16f893)){case _0x9c2f77(0x120):_0x23ee21+=_0x9c2f77(0xf8);break;case'error':_0x23ee21+=_0x9c2f77(0xc6);break;case _0x9c2f77(0x13d):_0x23ee21+=_0x9c2f77(0x17c);break;case _0x9c2f77(0x131):_0x23ee21+=_0x9c2f77(0x121);break;case'lag-med':_0x23ee21+='\x20ln-lag-med';break;case _0x9c2f77(0xce):_0x23ee21+='\x20ln-behind-light';break;case _0x9c2f77(0x141):_0x23ee21+=_0x9c2f77(0x165);break;default:_0x23ee21+='\x20ln-ok';}return _0x23ee21;},'linkMarker'(_0x116f3e){const _0x108d90=_0x4a26d6,_0x2ad1be=_0x116f3e[_0x108d90(0xe5)]?.['direction']??_0x108d90(0x11b);return _0x108d90(0x11b)!==_0x2ad1be&&'lo_to_hi'!==_0x2ad1be?'':_0x116f3e[_0x108d90(0xe5)]&&!_0x116f3e[_0x108d90(0xe5)]['enabled']?_0x108d90(0x180):_0x108d90(0xd4)+this['_healthMarker'](this[_0x108d90(0xd7)](_0x116f3e[_0x108d90(0x15c)]))+')';},'linkStartMarker'(_0x55e932){const _0x270615=_0x4a26d6,_0x23fc32=_0x55e932[_0x270615(0xe5)]?.['direction']??_0x270615(0x11b);return _0x270615(0x11b)!==_0x23fc32&&_0x270615(0x133)!==_0x23fc32?'':_0x55e932['link']&&!_0x55e932[_0x270615(0xe5)][_0x270615(0x14f)]?'url(#arrow-rev-disabled)':_0x270615(0x177)+this[_0x270615(0x14e)](this[_0x270615(0xd7)](_0x55e932[_0x270615(0x124)]))+')';}}),'mount'(_0x3e43e2){const _0x232a89=_0x4a26d6;this[_0x232a89(0x169)]=_0x3e43e2[_0x232a89(0x17b)]['querySelector'](_0x232a89(0x15b)),this[_0x232a89(0x156)]=this[_0x232a89(0x169)]?.[_0x232a89(0xbd)](_0x232a89(0x143)),this[_0x232a89(0x169)]&&(this[_0x232a89(0x169)][_0x232a89(0x139)]['cursor']=_0x232a89(0xdc)),this[_0x232a89(0xe0)](),this['_loadPositions'](),this[_0x232a89(0x178)]=new ResizeObserver(()=>this[_0x232a89(0xbb)]()),this[_0x232a89(0x178)][_0x232a89(0xe1)](_0x3e43e2[_0x232a89(0x17b)]),this[_0x232a89(0xde)]=setInterval(()=>{const _0x37d48d=_0x232a89;this[_0x37d48d(0x117)]=Date[_0x37d48d(0x117)]();},0x3e8),this['refreshTopology']()['then'](()=>this[_0x232a89(0xbb)]()),this[_0x232a89(0x12f)]=_0x5cf059=>{const _0x5a4ef6=_0x232a89;_0x5cf059[_0x5a4ef6(0x155)](),this[_0x5a4ef6(0x136)](_0x5cf059);},this[_0x232a89(0x169)]&&this[_0x232a89(0x169)][_0x232a89(0x116)](_0x232a89(0x10c),this['_wheelCb'],{'passive':!0x1}),this['_mousedownCb']=_0x33e6f8=>this[_0x232a89(0x108)](_0x33e6f8),this['_svgEl']&&this[_0x232a89(0x169)][_0x232a89(0x116)](_0x232a89(0x132),this[_0x232a89(0x13a)]),this['_mousemoveCb']=_0x53b9db=>this['_onMousemove'](_0x53b9db),this[_0x232a89(0x17d)]=()=>this[_0x232a89(0x122)](),window['addEventListener']('mousemove',this[_0x232a89(0xf1)]),window['addEventListener'](_0x232a89(0x171),this['_mouseupCb']),this[_0x232a89(0xda)]=()=>{const _0x9397de=_0x232a89;document['hidden']||this[_0x9397de(0x101)]();},document[_0x232a89(0x116)](_0x232a89(0x16b),this[_0x232a89(0xda)]),this[_0x232a89(0x130)]&&(this[_0x232a89(0x114)]=setInterval(()=>this[_0x232a89(0x101)](),this['autoRefreshIntervalMs'])),this[_0x232a89(0x17e)]=this[_0x232a89(0xef)],_0x3e43e2[_0x232a89(0xc5)](()=>_0x3e43e2[_0x232a89(0x154)][_0x232a89(0x17a)],_0xcfc93b=>{const _0x1ae585=_0x232a89;this[_0x1ae585(0x17e)]=_0xcfc93b??null,_0xcfc93b||(this[_0x1ae585(0x12b)]=null);});},'unmount'(){const _0x433893=_0x4a26d6;this[_0x433893(0x178)]?.[_0x433893(0x13b)](),clearInterval(this[_0x433893(0xde)]),clearInterval(this[_0x433893(0x114)]),this[_0x433893(0x169)]&&(this['_svgEl'][_0x433893(0x138)](_0x433893(0x10c),this['_wheelCb']),this['_svgEl'][_0x433893(0x138)](_0x433893(0x132),this['_mousedownCb'])),window['removeEventListener'](_0x433893(0x162),this[_0x433893(0xf1)]),window['removeEventListener']('mouseup',this[_0x433893(0x17d)]),document[_0x433893(0x138)](_0x433893(0x16b),this[_0x433893(0xda)]);},'template':_0x4a26d6(0xec)};
|
|
1
|
+
export default{tag:"sync-topology",unwrap:!0,context:e=>({w:900,h:500,layout:{cx:0,cy:0,r:0,pos:{}},_ro:null,_tick:null,now:Date.now(),selectedNodeId:null,selectedLinkKey:null,hoveredLinkKey:null,hoveredNodeId:null,lastUpdatedAt:null,autoRefresh:!0,autoRefreshIntervalMs:5e3,_autoRefreshTimer:null,loadingTopology:!1,editingLink:null,linkEditDir:"both",linkEditEnvs:null,linkEditEnabled:!0,savingLink:!1,panX:0,panY:0,zoom:1,_drag:null,_customPos:{},_svgEl:null,_groupEl:null,_applyTransform(){this._groupEl||(this._groupEl=this._svgEl?.querySelector(".topo-g")),this._groupEl&&this._groupEl.setAttribute("transform",`translate(${this.panX},${this.panY}) scale(${this.zoom})`)},_px2vb(){if(!this._svgEl)return{sx:1,sy:1};const e=this._svgEl.getBoundingClientRect();return{sx:this.w/(e.width||1),sy:this.h/(e.height||1)}},_onWheel(e){if(!this._svgEl)return;const t=this._svgEl.getBoundingClientRect(),{sx:i,sy:n}=this._px2vb(),s=(e.clientX-t.left)*i,o=(e.clientY-t.top)*n,l=e.deltaY<0?1.12:1/1.12,r=Math.max(.1,Math.min(8,this.zoom*l)),a=r/this.zoom;this.panX=s+(this.panX-s)*a,this.panY=o+(this.panY-o)*a,this.zoom=r,this._applyTransform()},_onSvgMousedown(e){if(0!==e.button)return;e.preventDefault();let t=null,i=e.target;for(;i&&i!==this._svgEl;){if(i.id&&i.id.startsWith("nd-")){t=i.id.slice(3);break}i=i.parentElement}if(t){const i=this._svgEl.getBoundingClientRect(),{sx:n,sy:s}=this._px2vb(),o=(e.clientX-i.left)*n,l=(e.clientY-i.top)*s,r=(o-this.panX)/this.zoom,a=(l-this.panY)/this.zoom,d=this._effectivePos(t)||{x:r,y:a};this._drag={type:"node",id:t,ox:r-d.x,oy:a-d.y}}else this._drag={type:"pan",sx:e.clientX,sy:e.clientY,px:this.panX,py:this.panY};this._svgEl&&(this._svgEl.style.cursor="grabbing")},_onMousemove(e){if(!this._drag)return;const{sx:t,sy:i}=this._px2vb();if("pan"===this._drag.type)this.panX=this._drag.px+(e.clientX-this._drag.sx)*t,this.panY=this._drag.py+(e.clientY-this._drag.sy)*i,this._applyTransform();else{const n=this._svgEl.getBoundingClientRect(),s=(e.clientX-n.left)*t,o=(e.clientY-n.top)*i,l=(s-this.panX)/this.zoom,r=(o-this.panY)/this.zoom;this._customPos={...this._customPos,[this._drag.id]:{x:l-this._drag.ox,y:r-this._drag.oy}},this._savePositions()}},_onMouseup(){this._drag=null,this._svgEl&&(this._svgEl.style.cursor="grab")},get externalSelectedNodeId(){return e.$attr.selected_node_id??null},get selfId(){return e.$attr.self_id},get topology(){return e.$attr.topology||{}},get availableEnvs(){return e.$attr.available_envs||[]},get envButtons(){const t=this.linkEditEnvs;return(e.$attr.available_envs||[]).map(e=>({name:e,style:null===t||Array.isArray(t)&&t.includes(e)?"border-color:var(--accent);color:var(--accent);background:var(--bg)":""}))},get dirButtons(){const e=this.editingLink;if(!e)return[];const t=e.lo.slice(0,6),i=e.hi.slice(0,6),n=this.linkEditDir;return[{canon:"both",label:t+" ⇄ "+i},{canon:"lo_to_hi",label:t+" → "+i},{canon:"hi_to_lo",label:i+" → "+t}].map(e=>({...e,style:e.canon===n?"border-color:var(--accent);color:var(--accent)":""}))},get nodes(){return Object.values(this.topology||{}).filter(e=>e&&null!=e.id).sort((e,t)=>String(e.id).localeCompare(String(t.id)))},get nodeList(){return this.nodes.map(e=>{const t=this._customPos[e.id]||this.layout.pos?.[e.id]||{x:this.w/2,y:this.h/2};return{...e,px:t.x,py:t.y}})},get links(){const e=this.topology||{},t=e[this.selfId]||Object.values(e).find(e=>null!=e?.links),i=t?.links||{},n=new Map,s=(e,t)=>({status:e.status||"idle",backlog:Math.max(0,Number(t??0)-Number(e.clock??0)),last_seen:e.last_seen??null,last_error:e.last_error??null});for(const t in e){const i=e[t];if(i?.peers)for(const o in i.peers){const l=t<o?t:o,r=t<o?o:t,a=l+":"+r;n.has(a)||n.set(a,{lo:l,hi:r,lo_pull:null,hi_pull:null});const d=n.get(a),c=i.peers[o];t===l?d.lo_pull=s(c,e[o]?.clock):d.hi_pull=s(c,e[o]?.clock)}}for(const e in i)if(i[e]&&!n.has(e)){const t=i[e];n.set(e,{lo:t.node_lo,hi:t.node_hi,lo_pull:null,hi_pull:null})}return[...n.entries()].map(([e,t])=>({key:e,lo:t.lo,hi:t.hi,link:i[e]||null,lo_pull:t.lo_pull,hi_pull:t.hi_pull}))},_linkConfig(e){const t=(this.topology||{})[this.selfId];return t?.links?.[e]||null},_linkHealth(e){if(e.link&&!e.link.enabled)return"disabled";const t=e.link?.direction??"both",i=[];"both"!==t&&"lo_to_hi"!==t||i.push(e.hi_pull),"both"!==t&&"hi_to_lo"!==t||i.push(e.lo_pull);const n=["error","lag-high","lag-med","behind","reconciling","stale","ok"];let s="stale";for(const e of i){const t=this._pullHealth(e);n.indexOf(t)<n.indexOf(s)&&(s=t)}return s},_pullHealth(e){if(!e)return"stale";if("reconciling"===e.status)return"reconciling";if(e.last_seen){const t=this.now-Number(e.last_seen);return e.backlog>50?"lag-high":e.backlog>5?"lag-med":e.backlog>0?"behind":t>3e4?"stale":"ok"}return e.last_error?"error":"stale"},_healthMarker:e=>"error"===e?"err":"reconciling"===e?"rec":"lag-high"===e?"lag-high":"lag-med"===e||"behind"===e||"stale"===e?"lag-med":"ok",_linkDirLabel(e){if(!e.link)return e.lo.slice(0,6)+" ⇄ "+e.hi.slice(0,6)+" (mesh)";if(!e.link.enabled)return"disabled";const t=e.lo.slice(0,6),i=e.hi.slice(0,6);switch(e.link.direction){case"both":return t+" ⇄ "+i;case"lo_to_hi":return t+" → "+i;case"hi_to_lo":return i+" → "+t;default:return e.link.direction}},get selection(){if(this.selectedLinkKey){const e=this.links.find(e=>e.key===this.selectedLinkKey)||null;if(e)return{type:"link",data:e}}if(this.selectedNodeId){const e=this.topology[this.selectedNodeId]||null;return e?{type:"node",id:this.selectedNodeId,data:e}:null}return null},get freshnessLabel(){if(!this.lastUpdatedAt)return"never";const e=this.now-this.lastUpdatedAt;return e<1500?"now":e<5e3?"few seconds ago":e<15e3?"recent":Math.floor(e/1e3)+"s ago"},get freshnessClass(){if(!this.lastUpdatedAt)return"fresh-stale";const e=this.now-this.lastUpdatedAt;return e<5e3?"fresh-now":e<15e3?"fresh-ok":"fresh-stale"},_recomputeLayout(){const e=this.nodes,t=this.w/2,i=this.h/2,n=Math.max(80,Math.min(t,i)-this.nodeRadius()-60),s={},o=e.length||1;e.forEach((e,l)=>{if(1===o)return void(s[e.id]={x:t,y:i});const r=2===o?0===l?Math.PI:0:l/o*Math.PI*2-Math.PI/2;s[e.id]={x:t+n*Math.cos(r),y:i+n*Math.sin(r)}}),this.layout={cx:t,cy:i,r:n,pos:s}},_measure(){const t=(this._svgEl||e.$el).getBoundingClientRect();this.w=Math.max(300,t.width||900),this.h=Math.max(200,t.height||500),this._recomputeLayout()},_effectivePos(e){return this._customPos[e]||this.layout.pos?.[e]||null},fitView(){this._measure();const e=this.nodes;if(!e.length)return;let t=1/0,i=1/0,n=-1/0,s=-1/0;for(const o of e){const e=this._effectivePos(o.id);e&&(t=Math.min(t,e.x),i=Math.min(i,e.y),n=Math.max(n,e.x),s=Math.max(s,e.y))}if(!isFinite(t))return;const o=this.nodeRadius()+60;t-=o,i-=o,n+=o,s+=o;const l=n-t,r=s-i,a=.88*Math.min(this.w/l,this.h/r,3);this.zoom=a,this.panX=this.w/2-(t+l/2)*a,this.panY=this.h/2-(i+r/2)*a,this._applyTransform()},resetLayout(){this._customPos={};try{localStorage.removeItem("okdb-topo-pos")}catch{}this.fitView()},_savePositions(){try{localStorage.setItem("okdb-topo-pos",JSON.stringify(this._customPos))}catch{}},_loadPositions(){try{const e=localStorage.getItem("okdb-topo-pos");e&&(this._customPos=JSON.parse(e))}catch{}},nodeRadius:()=>22,linkPath(e){const t=this._effectivePos(e.lo),i=this._effectivePos(e.hi);if(!t||!i)return"";const n=i.x-t.x,s=i.y-t.y,o=Math.sqrt(n*n+s*s)||1,l=this.nodeRadius();return"M "+(t.x+n/o*l)+" "+(t.y+s/o*l)+" L "+(i.x-n/o*l)+" "+(i.y-s/o*l)},reconciling:!1,reconcilingTarget:null,async refreshTopology(){if(!this.loadingTopology){this.loadingTopology=!0;try{const t=await fetch("/api/sync/topology"),i=await t.json(),n=i.data??i;e.$attr.topology=n.topology??n,e.$attr.available_envs=n.availableEnvs??[],this.lastUpdatedAt=Date.now()}catch(e){console.error("refresh failed",e)}finally{this.loadingTopology=!1}}},async reconcileAll(){if(!this.reconciling){this.reconciling=!0,this.reconcilingTarget="all";try{await fetch("/api/sync/reconcile-all",{method:"POST"}),await this.refreshTopology()}finally{this.reconciling=!1,this.reconcilingTarget=null}}},async reconcileNode(e){if(!this.reconciling){this.reconciling=!0,this.reconcilingTarget=e;try{e===this.selfId?await fetch("/api/sync/reconcile",{method:"POST"}):await fetch(this.topology[e]?.address+"/api/sync/reconcile",{method:"POST"}),await this.refreshTopology()}finally{this.reconciling=!1,this.reconcilingTarget=null}}},async reconcileLink(e){if(this.reconciling)return;this.reconciling=!0,this.reconcilingTarget=e.key;const t=e.lo===this.selfId?e.hi:e.lo;try{await fetch("/api/sync/reconcile-peer",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({peer_id:t})}),await this.refreshTopology()}finally{this.reconciling=!1,this.reconcilingTarget=null}},openLinkEditor(e){this.editingLink=e,this.linkEditDir=e.link?.direction??"both",this.linkEditEnvs=e.link?.envs??null,this.linkEditEnabled=!1!==e.link?.enabled},closeLinkEditor(){this.editingLink=null},toggleLinkEnv(e){if(null===this.linkEditEnvs)this.linkEditEnvs=this.availableEnvs.filter(t=>t!==e);else{const t=new Set(this.linkEditEnvs);t.has(e)?t.delete(e):t.add(e);const i=[...t];this.linkEditEnvs=i.length===this.availableEnvs.length?null:i}},async saveLink(){if(!this.editingLink||this.savingLink)return;this.savingLink=!0;const e=this.editingLink;try{const t=await fetch("/api/sync/links/"+e.lo+"/"+e.hi,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({direction:this.linkEditDir,envs:this.linkEditEnvs,enabled:this.linkEditEnabled})});if(!t.ok){const e=await t.json();throw new Error(e?.error?.message||"Save failed")}this.closeLinkEditor(),await this.refreshTopology()}catch(e){alert(e.message)}finally{this.savingLink=!1}},async deleteLink(){if(!this.editingLink||this.savingLink)return;if(!confirm("Remove this data link? Nodes stay connected but data will stop syncing."))return;this.savingLink=!0;const e=this.editingLink;try{await fetch("/api/sync/links/"+e.lo+"/"+e.hi,{method:"DELETE"}),this.closeLinkEditor(),await this.refreshTopology()}catch(e){alert(e.message)}finally{this.savingLink=!1}},deselect(){this.selectedNodeId=null,this.selectedLinkKey=null,this.hoveredLinkKey=null,this.hoveredNodeId=null,this.editingLink=null,e.$dispatch?.("select",null),e.$dispatch?.("selectLink",null)},pickNode(t){this.selectedNodeId=t,this.selectedLinkKey=null,this.editingLink=null,e.$dispatch?.("select",t),e.$dispatch?.("selectLink",null)},pickLink(t){const i=this.links.find(e=>e.key===t)||null;if(i&&(!this.selectedNodeId||i.lo===this.selectedNodeId||i.hi===this.selectedNodeId)){if(this.selectedLinkKey===t)return this.selectedLinkKey=null,this.editingLink=null,void e.$dispatch?.("selectLink",null);this.selectedLinkKey=t,this.selectedNodeId||(this.selectedNodeId=i.lo,e.$dispatch?.("select",this.selectedNodeId)),e.$dispatch?.("selectLink",t)}},async copy(e){try{await navigator.clipboard.writeText(String(e??""))}catch(e){}},_sPointerEventsFor(e){return this.selectedNodeId&&e.lo!==this.selectedNodeId&&e.hi!==this.selectedNodeId?"pointer-events:none":"pointer-events:stroke;cursor:pointer"},_sEnabledBadge:e=>e?"color:var(--ok)":"color:var(--dis);opacity:0.7",_sEnabledBtn:e=>e?"border-color:var(--ok);color:var(--ok)":"border-color:var(--dis);color:var(--dis)",nodeHealth(e){const t=this.links.filter(t=>t.lo===e||t.hi===e);if(!t.length)return"ok";const i=t.map(e=>this._linkHealth(e));return i.includes("error")?"error":i.includes("lag-high")?"critical":i.includes("lag-med")?"lagging":i.includes("behind")?"behind":i.every(e=>"stale"===e||"disabled"===e)?"stale":"ok"},isLinkInteractable(e){return!this.selectedNodeId||e.lo===this.selectedNodeId||e.hi===this.selectedNodeId},isActiveLink(e){return this.selectedLinkKey===e.key||this.hoveredLinkKey===e.key&&this.isLinkInteractable(e)},nodeClass(e){let t="nd";const i=this.selectedNodeId,n=this.hoveredNodeId;if(i&&i!==e.id&&(t+=" nd-dim"),i===e.id&&(t+=" nd-sel"),this.selectedLinkKey){const n=this.links.find(e=>e.key===this.selectedLinkKey);!n||n.lo!==e.id&&n.hi!==e.id||i===e.id||(t+=" nd-sel2")}i||n!==e.id||(t+=" nd-hover");const s=this.nodeHealth(e.id);return"error"===s?t+=" nd-err":"critical"===s?t+=" nd-critical":"lagging"===s?t+=" nd-lag":"behind"===s?t+=" nd-behind":"stale"===s&&(t+=" nd-stale"),t},linkClass(e){let t="ln";const i=this.selectedLinkKey===e.key,n=this.hoveredLinkKey===e.key&&this.isLinkInteractable(e);switch(this.isLinkInteractable(e)||i||(t+=" ln-dim"),i?t+=" ln-selected":n&&(t+=" ln-hover"),this._linkHealth(e)){case"disabled":t+=" ln-disabled";break;case"error":t+=" ln-err";break;case"reconciling":t+=" ln-rec";break;case"lag-high":t+=" ln-lag-high";break;case"lag-med":t+=" ln-lag-med";break;case"behind":t+=" ln-behind-light";break;case"stale":t+=" ln-stale";break;default:t+=" ln-ok"}return t},linkMarker(e){const t=e.link?.direction??"both";return"both"!==t&&"lo_to_hi"!==t?"":e.link&&!e.link.enabled?"url(#arrow-disabled)":"url(#arrow-"+this._healthMarker(this._pullHealth(e.hi_pull))+")"},linkStartMarker(e){const t=e.link?.direction??"both";return"both"!==t&&"hi_to_lo"!==t?"":e.link&&!e.link.enabled?"url(#arrow-rev-disabled)":"url(#arrow-rev-"+this._healthMarker(this._pullHealth(e.lo_pull))+")"}}),mount(e){this._svgEl=e.$el.querySelector("svg"),this._groupEl=this._svgEl?.querySelector(".topo-g"),this._svgEl&&(this._svgEl.style.cursor="grab"),this._measure(),this._loadPositions(),this._ro=new ResizeObserver(()=>this.fitView()),this._ro.observe(e.$el),this._tick=setInterval(()=>{this.now=Date.now()},1e3),this.refreshTopology().then(()=>this.fitView()),this._wheelCb=e=>{e.preventDefault(),this._onWheel(e)},this._svgEl&&this._svgEl.addEventListener("wheel",this._wheelCb,{passive:!1}),this._mousedownCb=e=>this._onSvgMousedown(e),this._svgEl&&this._svgEl.addEventListener("mousedown",this._mousedownCb),this._mousemoveCb=e=>this._onMousemove(e),this._mouseupCb=()=>this._onMouseup(),window.addEventListener("mousemove",this._mousemoveCb),window.addEventListener("mouseup",this._mouseupCb),this._visibilityHandler=()=>{document.hidden||this.refreshTopology()},document.addEventListener("visibilitychange",this._visibilityHandler),this.autoRefresh&&(this._autoRefreshTimer=setInterval(()=>this.refreshTopology(),this.autoRefreshIntervalMs)),this.selectedNodeId=this.externalSelectedNodeId,e.$watch(()=>e.$attr.selected_node_id,e=>{this.selectedNodeId=e??null,e||(this.selectedLinkKey=null)})},unmount(){this._ro?.disconnect(),clearInterval(this._tick),clearInterval(this._autoRefreshTimer),this._svgEl&&(this._svgEl.removeEventListener("wheel",this._wheelCb),this._svgEl.removeEventListener("mousedown",this._mousedownCb)),window.removeEventListener("mousemove",this._mousemoveCb),window.removeEventListener("mouseup",this._mouseupCb),document.removeEventListener("visibilitychange",this._visibilityHandler)},template:'<div style="--bg:var(--okdb-app-bg-deep); --panel:var(--okdb-app-surface); --panel2:var(--okdb-app-bg-deep); --border:var(--okdb-app-border); --border2:var(--okdb-app-surface-2);\n --text:var(--okdb-app-fg); --muted:var(--okdb-app-fg-muted); --accent:var(--okdb-accent); --ok:var(--okdb-success); --rec:var(--okdb-accent);\n --warn:var(--okdb-warning); --bad:var(--okdb-danger); --err:var(--okdb-danger); --dis:var(--okdb-app-fg-subtle);\n border:1px solid var(--border); border-radius:12px; background:var(--bg); color:var(--text);\n display:flex; flex-direction:column; flex:1; position:relative; overflow:hidden;\n font-family: ui-monospace, monospace;"><div style="padding:12px 14px;display:flex;justify-content:space-between;border-bottom:1px solid var(--border2);background:var(--panel);align-items:center;"><div style="display:flex;align-items:center;gap:10px;"><div style="font-weight:700;letter-spacing:0.8px;color:var(--accent);">NETWORK_TOPOLOGY</div><button @click="reconcileAll()" :disabled="reconciling" style="font-size:11px;padding:4px 10px;border-radius:8px;border:1px solid var(--border);background:var(--panel2);color:var(--accent);cursor:pointer;"><span style="display:flex;align-items:center;gap:6px;"><span :style="reconcilingTarget===\'all\'?\'display:inline-block\':\'display:none\'" class="spinner"/> {{reconcilingTarget===\'all\'?\'RECONCILING\':\'RECONCILE ALL\'}}</span></button> <button @click="refreshTopology()" :disabled="loadingTopology" style="font-size:11px;padding:4px 10px;border-radius:8px;border:1px solid var(--border);background:var(--panel2);color:var(--muted);cursor:pointer;">REFRESH</button> <button @click="fitView()" style="font-size:11px;padding:4px 10px;border-radius:8px;border:1px solid var(--border);background:var(--panel2);color:var(--muted);cursor:pointer;" title="Fit all nodes into view">FIT</button> <button @click="resetLayout()" style="font-size:11px;padding:4px 10px;border-radius:8px;border:1px solid var(--border);background:var(--panel2);color:var(--muted);cursor:pointer;" title="Reset node positions">RESET</button> <span :style="{opacity:loadingTopology?1:0}" class="spinner"/></div><div style="display:flex;align-items:center;gap:12px;font-size:11px;"><span :class="freshnessClass">updated: {{freshnessLabel}}</span> <span style="opacity:0.75;">{{nodes.length}} NODES // {{links.length}} LINKS</span></div></div><div style="flex:1;position:relative;"><svg :viewBox="\'0 0 \'+w+\' \'+h" style="width:100%;height:100%;"><rect :width="w" :height="h" fill="transparent" style="pointer-events:all" @click="deselect()"/><defs><marker id="arrow-ok" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" fill="var(--ok)"/></marker><marker id="arrow-rec" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" fill="var(--rec)"/></marker><marker id="arrow-err" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" fill="var(--err)"/></marker><marker id="arrow-lag-med" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" fill="var(--warn)"/></marker><marker id="arrow-lag-high" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" fill="var(--bad)"/></marker><marker id="arrow-disabled" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" fill="var(--dis)"/></marker><marker id="arrow-rev-ok" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="var(--ok)"/></marker><marker id="arrow-rev-rec" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="var(--rec)"/></marker><marker id="arrow-rev-err" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="var(--err)"/></marker><marker id="arrow-rev-lag-med" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="var(--warn)"/></marker><marker id="arrow-rev-lag-high" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="var(--bad)"/></marker><marker id="arrow-rev-disabled" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="var(--dis)"/></marker></defs><style>.ln { fill:none; stroke-width:1.9; opacity:0.70; transition:opacity 0.14s,stroke-width 0.14s,filter 0.14s; }\n .ln-ok { stroke:var(--ok); }\n .ln-behind-light { stroke:var(--okdb-info); opacity:0.8; }\n .ln-rec { stroke:var(--rec); stroke-dasharray:4 3; animation:dash 1.1s linear infinite; filter:drop-shadow(0 0 4px rgba(77,163,255,0.5)); }\n .ln-lag-med { stroke:var(--warn); }\n .ln-lag-high { stroke:var(--bad); }\n .ln-err { stroke:var(--err); }\n .ln-stale { stroke:var(--okdb-app-fg-subtle); opacity:0.5; }\n .ln-disabled { stroke:var(--dis); stroke-dasharray:6 4; opacity:0.45; }\n .ln-dim { opacity:0.08; filter:none !important; }\n .ln-hover { stroke-width:3.0; opacity:1; }\n .ln-selected { stroke:var(--accent) !important; stroke-width:5.2 !important; opacity:1 !important;\n filter:drop-shadow(0 0 4px rgba(77,163,255,0.9)) drop-shadow(0 0 10px rgba(77,163,255,0.35)); }\n\n .nd circle { fill:var(--bg); stroke:var(--border); stroke-width:2; transition:stroke 0.14s,stroke-width 0.14s,filter 0.14s,opacity 0.14s; }\n .nd text { fill:var(--muted); font-size:11px; pointer-events:none; }\n .nd-hover circle { stroke:rgba(77,163,255,0.8); }\n .nd-sel circle { stroke:var(--accent); stroke-width:3.4; filter:drop-shadow(0 0 4px rgba(77,163,255,0.75)) drop-shadow(0 0 10px rgba(77,163,255,0.25)); }\n .nd-sel2 circle { stroke:rgba(77,163,255,0.75); stroke-width:3.0; filter:drop-shadow(0 0 6px rgba(77,163,255,0.25)); }\n .nd-dim { opacity:0.22; }\n .nd-err circle { stroke:var(--err); }\n .nd-stale circle { stroke:var(--okdb-app-fg-subtle); stroke-width:2; opacity:0.7; }\n .nd-behind circle { stroke:var(--okdb-info); stroke-width:2.5; }\n .nd-lag circle { stroke:var(--warn); stroke-width:3; }\n .nd-critical circle { stroke:var(--bad); stroke-width:3.5; filter:drop-shadow(0 0 6px rgba(255,77,77,0.5)); }\n .fresh-now { color:var(--ok); }\n .fresh-ok { color:var(--okdb-info); }\n .fresh-stale { color:var(--warn); }\n .spinner { display:inline-block; width:12px; height:12px; border:2px solid rgba(77,163,255,0.2); border-top:2px solid var(--accent); border-radius:50%; animation:spin 0.8s linear infinite; flex-shrink:0; }\n @keyframes dash { to { stroke-dashoffset:-14; } }\n @keyframes spin { to { transform:rotate(360deg); } }</style><g class="topo-g"><for :="links" let:l><if :="!isActiveLink(l)"><g><path :d="linkPath(l)" stroke="transparent" stroke-width="14" fill="none" :style="_sPointerEventsFor(l)" @mouseenter="isLinkInteractable(l)?(hoveredLinkKey=l.key):null" @mouseleave="hoveredLinkKey=null" @click="pickLink(l.key)"/><path :d="linkPath(l)" :class="linkClass(l)" :marker-end="linkMarker(l)" :marker-start="linkStartMarker(l)"><title>{{l.lo.slice(0,8)}} ↔ {{l.hi.slice(0,8)}} direction: {{l.link ? l.link.direction : \'mesh\'}} enabled: {{l.link ? l.link.enabled : true}} envs: {{l.link && l.link.envs ? l.link.envs.join(\', \') : \'all\'}}</title></path></g></if></for><for :="nodeList" let:n><g :id="\'nd-\'+n.id" :transform="\'translate(\'+n.px+\',\'+n.py+\')\'" :class="nodeClass(n)" @mouseenter="hoveredNodeId=n.id" @mouseleave="hoveredNodeId=null" @click="pickNode(n.id)"><circle :r="nodeRadius()"/><text text-anchor="middle" dy="4">{{n.id.slice(0,6)}}</text><title>{{n.id}} clock: {{n.clock ?? \'--\'}} addr: {{n.address ?? \'—\'}}</title></g></for><for :="links" let:l><if :="isActiveLink(l)"><g><path :d="linkPath(l)" stroke="transparent" stroke-width="16" fill="none" :style="_sPointerEventsFor(l)" @mouseenter="isLinkInteractable(l)?(hoveredLinkKey=l.key):null" @mouseleave="hoveredLinkKey=null" @click="pickLink(l.key)"/><path :d="linkPath(l)" :class="linkClass(l)" :marker-end="linkMarker(l)" :marker-start="linkStartMarker(l)"/></g></if></for></g></svg><if :="selection || editingLink"><div style="position:absolute;top:18px;right:18px;width:320px;background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px;box-shadow:0 16px 34px rgba(0,0,0,0.45);border-left:4px solid var(--accent);"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;"><span style="font-size:10px;background:var(--bg);padding:3px 7px;border-radius:6px;color:var(--accent);letter-spacing:0.6px;">{{editingLink ? \'LINK_CONFIG\' : selection.type.toUpperCase()}} </span><span @click="editingLink ? closeLinkEditor() : deselect()" style="cursor:pointer;opacity:0.7;font-size:16px;line-height:12px;">×</span></div><if :="!editingLink && selection && selection.type === \'node\'"><div style="font-size:13px;"><div style="display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px;"><div style="color:var(--text);font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{{selection.id}}</div><button @click="copy(selection.id)" style="border:1px solid var(--border);background:var(--panel2);color:var(--muted);font-size:11px;padding:4px 8px;border-radius:8px;cursor:pointer;">copy</button></div><div style="display:grid;grid-template-columns:92px 1fr;row-gap:8px;column-gap:10px;"><div style="opacity:0.65;">Clock</div><div>{{selection.data.clock??\'--\'}}</div><div style="opacity:0.65;">Addr</div><div style="font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{{selection.data.address||\'N/A\'}}</div><div style="opacity:0.65;">Status</div><div :style="selection.data.error?\'color:var(--err)\':\'color:var(--ok)\'">{{selection.data.error?(\'ERR: \'+selection.data.error):\'OK\'}}</div></div><div style="margin-top:12px;border-top:1px solid var(--border2);padding-top:10px;"><div style="font-size:11px;opacity:0.7;margin-bottom:6px;">Tags</div><div style="display:flex;flex-wrap:wrap;gap:6px;"><if :="!(selection.data.tags || []).length"><span style="font-size:11px;opacity:0.45;">No tags</span></if><for :="selection.data.tags || []" let:tag><span style="font-size:10px;padding:2px 8px;border-radius:999px;border:1px solid var(--okdb-accent-soft-border);color:var(--okdb-accent);background:var(--okdb-accent-soft-bg);">{{tag}}</span></for></div></div><div style="margin-top:12px;border-top:1px solid var(--border2);padding-top:10px;"><div style="font-size:11px;opacity:0.7;margin-bottom:6px;">Available envs</div><div style="font-size:11px;color:var(--muted);line-height:1.5;word-break:break-word;">{{(selection.data.availableEnvs || []).join(\', \') || \'—\'}}</div></div><div style="margin-top:12px;border-top:1px solid var(--border2);padding-top:10px;"><div style="font-size:11px;opacity:0.7;margin-bottom:6px;">Outbound peers</div><div style="display:flex;flex-wrap:wrap;gap:6px;"><for :="Object.keys(selection.data.peers||{})" let:pid><span style="font-size:11px;padding:3px 7px;border:1px solid var(--border);background:var(--panel2);border-radius:999px;color:var(--muted);">{{pid.slice(0,8)}}</span></for></div></div><div style="margin-top:14px;"><button @click="reconcileNode(selection.id)" :disabled="reconciling" style="width:100%;box-sizing:border-box;padding:6px 10px;border-radius:8px;border:1px solid var(--border);background:var(--panel2);color:var(--accent);cursor:pointer;font-size:12px;"><span style="display:flex;align-items:center;justify-content:center;gap:6px;"><span :style="reconcilingTarget===selection.id?\'display:inline-block\':\'display:none\'" class="spinner"/> {{reconcilingTarget===selection.id?\'RECONCILING\':\'RECONCILE NODE\'}}</span></button></div></div></if><if :="!editingLink && selection && selection.type === \'link\'"><div style="font-size:13px;"><div style="margin-bottom:12px;"><div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;"><span style="font-family:monospace;font-size:12px;font-weight:700;">{{selection.data.lo.slice(0,8)}}</span> <span style="font-size:16px;color:var(--accent);">{{selection.data.link ? (selection.data.link.direction === \'lo_to_hi\' ? \'→\' : selection.data.link.direction === \'hi_to_lo\' ? \'←\' : \'⇄\') : \'⇄\'}} </span><span style="font-family:monospace;font-size:12px;font-weight:700;">{{selection.data.hi.slice(0,8)}}</span><if :="selection.data.link"><span style="font-size:11px;margin-left:4px;" :style="_sEnabledBadge(selection.data.link.enabled)">{{selection.data.link.enabled ? \'● on\' : \'○ off\'}}</span></if></div><if :="selection.data.link && selection.data.link.envs"><div style="font-size:11px;opacity:0.45;">envs: {{selection.data.link.envs.join(\', \')}}</div></if><if :="!selection.data.link"><div style="font-size:11px;opacity:0.45;">no link config — mesh (legacy)</div></if></div><div style="border:1px solid var(--border2);border-radius:8px;overflow:hidden;margin-bottom:12px;font-size:11px;"><if :="!selection.data.link || selection.data.link.direction === \'both\' || selection.data.link.direction === \'lo_to_hi\'"><div style="padding:9px 11px;border-bottom:1px solid var(--bg);"><div style="display:flex;align-items:center;gap:6px;margin-bottom:6px;"><span style="font-family:monospace;opacity:0.7;">{{selection.data.lo.slice(0,6)}}</span> <span style="color:var(--accent);">→</span> <span style="font-family:monospace;opacity:0.7;">{{selection.data.hi.slice(0,6)}}</span> <span style="opacity:0.4;">(hi receives from lo)</span></div><div style="display:grid;grid-template-columns:70px 1fr;row-gap:4px;gap:6px;opacity:0.85;"><span style="opacity:0.5;">status</span> <span>{{selection.data.hi_pull ? selection.data.hi_pull.status : \'—\'}}</span> <span style="opacity:0.5;">backlog</span> <span>{{selection.data.hi_pull ? selection.data.hi_pull.backlog : \'—\'}}</span> <span style="opacity:0.5;">last seen</span> <span>{{selection.data.hi_pull && selection.data.hi_pull.last_seen ? (Math.round((now - selection.data.hi_pull.last_seen)/1000) + \'s ago\') : \'—\'}}</span><if :="selection.data.hi_pull && selection.data.hi_pull.last_error"><span style="opacity:0.5;">error</span> <span style="color:var(--err);">{{selection.data.hi_pull.last_error}}</span></if></div></div></if><if :="!selection.data.link || selection.data.link.direction === \'both\' || selection.data.link.direction === \'hi_to_lo\'"><div style="padding:9px 11px;"><div style="display:flex;align-items:center;gap:6px;margin-bottom:6px;"><span style="font-family:monospace;opacity:0.7;">{{selection.data.hi.slice(0,6)}}</span> <span style="color:var(--accent);">→</span> <span style="font-family:monospace;opacity:0.7;">{{selection.data.lo.slice(0,6)}}</span> <span style="opacity:0.4;">(lo receives from hi)</span></div><div style="display:grid;grid-template-columns:70px 1fr;row-gap:4px;gap:6px;opacity:0.85;"><span style="opacity:0.5;">status</span> <span>{{selection.data.lo_pull ? selection.data.lo_pull.status : \'—\'}}</span> <span style="opacity:0.5;">backlog</span> <span>{{selection.data.lo_pull ? selection.data.lo_pull.backlog : \'—\'}}</span> <span style="opacity:0.5;">last seen</span> <span>{{selection.data.lo_pull && selection.data.lo_pull.last_seen ? (Math.round((now - selection.data.lo_pull.last_seen)/1000) + \'s ago\') : \'—\'}}</span><if :="selection.data.lo_pull && selection.data.lo_pull.last_error"><span style="opacity:0.5;">error</span> <span style="color:var(--err);">{{selection.data.lo_pull.last_error}}</span></if></div></div></if></div><div style="display:flex;flex-direction:column;gap:7px;"><button @click="reconcileLink(selection.data)" :disabled="reconciling" style="width:100%;box-sizing:border-box;padding:7px;border-radius:8px;border:1px solid var(--border);background:var(--panel2);color:var(--accent);cursor:pointer;font-size:12px;"><span style="display:flex;align-items:center;justify-content:center;gap:6px;"><span :style="reconcilingTarget===selection.data.key?\'display:inline-block\':\'display:none\'" class="spinner"/> {{reconcilingTarget===selection.data.key ? \'Reconciling…\' : \'Reconcile\'}}</span></button> <button @click="openLinkEditor(selection.data)" style="width:100%;box-sizing:border-box;padding:7px;border-radius:8px;border:1px solid var(--border);background:var(--panel2);color:var(--muted);cursor:pointer;font-size:12px;">✎ Configure Link</button></div></div></if><if :="editingLink"><div style="font-size:13px;"><div style="margin-bottom:14px;"><div style="font-family:monospace;font-size:11px;opacity:0.5;margin-bottom:2px;">link id</div><div style="font-family:monospace;font-size:11px;">{{editingLink.key}}</div></div><div style="margin-bottom:14px;"><div style="font-size:11px;opacity:0.5;margin-bottom:7px;">Direction</div><div style="display:flex;flex-direction:column;gap:5px;"><for :="dirButtons" let:btn><button @click="linkEditDir = btn.canon" :style="btn.style" style="width:100%;box-sizing:border-box;padding:7px 10px;border-radius:7px;border:1px solid var(--border);background:var(--panel2);color:var(--muted);cursor:pointer;font-size:12px;font-family:monospace;text-align:left;">{{btn.label}}</button></for></div></div><if :="availableEnvs.length"><div style="margin-bottom:14px;"><div style="font-size:11px;opacity:0.5;margin-bottom:7px;">Data envs — {{linkEditEnvs === null ? \'all\' : linkEditEnvs.length + \' selected\'}}</div><div style="display:flex;flex-wrap:wrap;gap:5px;"><for :="envButtons" let:btn><button @click="toggleLinkEnv(btn.name)" :style="btn.style" style="padding:4px 10px;border-radius:6px;border:1px solid var(--border);background:var(--panel2);color:var(--muted);cursor:pointer;font-size:11px;">{{btn.name}}</button></for></div></div></if><div style="margin-bottom:16px;display:flex;align-items:center;gap:10px;"><span style="font-size:11px;opacity:0.5;">Enabled</span> <button @click="linkEditEnabled=!linkEditEnabled" :style="_sEnabledBtn(linkEditEnabled)" style="padding:4px 12px;border-radius:6px;border:1px solid;background:var(--panel2);cursor:pointer;font-size:11px;">{{linkEditEnabled ? \'● ON\' : \'○ OFF\'}}</button></div><div style="display:flex;gap:8px;"><button @click="saveLink()" :disabled="savingLink" style="flex:1;padding:8px;border-radius:8px;border:1px solid var(--accent);background:rgba(77,163,255,0.1);color:var(--accent);cursor:pointer;font-size:12px;"><span style="display:flex;align-items:center;justify-content:center;gap:5px;"><span :style="savingLink?\'display:inline-block\':\'display:none\'" class="spinner"/> Save</span></button> <button @click="deleteLink()" :disabled="savingLink" style="padding:8px 14px;border-radius:8px;border:1px solid #3d1a1a;background:rgba(255,59,48,0.08);color:var(--err);cursor:pointer;font-size:12px;">Remove</button></div></div></if></div></if></div><div style="padding:9px 14px;background:var(--bg);border-top:1px solid var(--border2);display:flex;gap:16px;font-size:11px;align-items:center;flex-wrap:wrap;opacity:0.9;"><span><span style="color:var(--ok);">●</span> synced</span> <span><span style="color:var(--okdb-info);">●</span> behind</span> <span><span style="color:var(--rec);">●</span> reconciling</span> <span><span style="color:var(--warn);">●</span> lag</span> <span><span style="color:var(--err);">●</span> error</span> <span><span style="color:var(--dis);">●</span> disabled</span> <span style="opacity:0.4;">⇄ both → send ← receive -- disabled (click to inspect)</span></div></div>'};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<script type="module">import createEnvModal from"../db/modals/create-env-modal.ok.js";import demoEnvModal from"../db/modals/demo-env-modal.ok.js";import removeEnvModal from"../db/modals/remove-env-modal.ok.js";import dataOpsModal from"./modals/data-ops-modal.ok.js";import systemLog from"./parts/system-log.ok.js";import systemLogHistory from"./parts/system-log-history.ok.js";import systemSyncPanel from"./parts/system-sync-panel.ok.js";import systemAuthPanel from"./parts/system-auth-panel.ok.js";import systemApiPanel from"./parts/system-api-panel.ok.js";import licensePanel from"../license/parts/license-panel.ok.js";import docViewer from"@toolkit/components/ok-doc-viewer.ok.js";export default{tag:"system-index",unwrap:!0,dependencies:[systemLog,systemLogHistory,systemSyncPanel,systemAuthPanel,systemApiPanel,licensePanel,docViewer,createEnvModal,demoEnvModal,removeEnvModal,dataOpsModal],context:e=>({_systemWildcard(){const t=e.$vars.$router?.match("/system/*");return t?.params?.["*"]??""},get subView(){return this._parseSubView(this._systemWildcard())},_parseSubView(e){const t=e??"";return t&&"overview"!==t?"logs"===t?"logs":"events"===t?"events":"sync"===t?"sync":"api"===t?"api":"auth"===t?"auth":"license"===t?"license":t.startsWith("docs")?"docs":"overview":"overview"},goToSystemSub(t){const o=e.$vars.$router;if(!o)return;const s=e.$ok.stores.okdb.selectedEnv,n=s?`?env=${encodeURIComponent(s)}`:"";o.push(`/system/${t}${n}`)},docsManifest:[],docsAnchor:"",get docsCurrentFile(){return this._parseDocsFile(this._systemWildcard())},_parseDocsFile(e){const t=e??"",o=t.startsWith("docs/")?t.slice(5):"";return o?o.replace(/\.md$/,"")+".md":"index.md"},docsIsActive(e){return this.docsCurrentFile===e},docsNavigateTo(e){const t=e.replace(/\.md$/,"");this.docsAnchor="",this.goToSystemSub("docs/"+t)},docsOnAnchorChange(e){this.docsAnchor=e},async loadDocsManifest(){if(!this.docsManifest.length)try{const e=await fetch("/docs/manifest.json");e.ok&&(this.docsManifest=await e.json())}catch{}},info:null,runtime:null,syncInfo:null,envs:[],loading:!1,error:null,autoRefresh:!0,_timer:null,metrics:null,async load(){this.loading=!0,this.error=null;try{const e=await fetch(`${__ENV__.API}/info`),t=await e.json();if(!e.ok)throw new Error(t?.error?.message||"Failed to load info");this.info=t.data??t}catch(e){this.error=e?.message||String(e)}finally{this.loading=!1}try{const t=await fetch(`${__ENV__.API}/envs`);if(t.ok){const o=await t.json();this.envs=o.data??[],e.$ok.stores.okdb.envs=this.envs}}catch{}try{const e=await fetch(`${__ENV__.API}/system/runtime`);if(e.ok){const t=await e.json();this.runtime=t.data??t}}catch{}try{const e=await fetch(`${__ENV__.API}/system/metrics`);if(e.ok){const t=await e.json();this.metrics=t.data??null}}catch{}try{const e=await fetch(`${__ENV__.API}/sync/info`);this.syncInfo=e.ok?(await e.json()).data??null:null}catch{this.syncInfo=null}},get approxSize(){const e=(this.envs||[]).reduce((e,t)=>e+(t.size||0),0);if(!e)return"--";const t=["B","KB","MB","GB","TB"];let o=e,s=0;for(;o>=1024&&s<t.length-1;)o/=1024,s++;return o.toFixed(2)+" "+t[s]},get typeList(){const e=[];for(const t of this.envs||[])for(const o of t.types||[])o.name.startsWith("~")||e.push({name:o.name,env:t.name,count:o.count??0});return e.sort((e,t)=>e.name.localeCompare(t.name)||e.env.localeCompare(t.env))},get systemTypeList(){const e=[];for(const t of this.envs||[])for(const o of t.types||[])o.name.startsWith("~")&&e.push({name:o.name,env:t.name,count:o.count??0});return e.sort((e,t)=>e.name.localeCompare(t.name)||e.env.localeCompare(t.env))},get totalTypeCount(){return this.typeList.length},get plugins(){return(this.info?.plugins||[]).filter(Boolean)},get config(){return this.info?.config??null},get durabilityLabel(){const e=this.config?.durability;return{strict:{label:"Strict",color:"var(--okdb-success)",hint:"Full fsync on every commit."},balanced:{label:"Balanced",color:"var(--okdb-accent)",hint:"Async fsync worker — crash-safe, fast."},fast:{label:"Fast",color:"var(--okdb-warning)",hint:"No per-commit fsync. May lose last tx on crash."},custom:{label:"Custom",color:"var(--okdb-purple)",hint:"Manual LMDB flags. No preset applied."}}[e]??{label:e??"--",color:"var(--okdb-app-fg-muted)",hint:""}},get configWarnings(){return this.config?.warnings??[]},formatBytes(e){if(null==e)return"--";const t=["B","KB","MB","GB","TB"];let o=e,s=0;for(;o>=1024&&s<t.length-1;)o/=1024,s++;return o.toFixed(0===s?0:2)+" "+t[s]},formatUptime(e){if(null==e)return"--";const t=Math.floor(e),o=Math.floor(t/86400),s=Math.floor(t%86400/3600),n=Math.floor(t%3600/60),a=t%60;return o>0?`${o}d ${s}h ${n}m`:s>0?`${s}h ${n}m ${a}s`:n>0?`${n}m ${a}s`:`${a}s`},formatLoad:e=>e&&e.length?e.map(e=>e.toFixed(2)).join(" / "):"--",pct:(e,t)=>e&&t?Math.round(e/t*100):0,get clock(){return this.syncInfo?.clock??this.info?.clock??"--"},get nodeId(){return this.syncInfo?.node?.id??this.info?.id??"--"},get nodeAddress(){return this.syncInfo?.node?.address??null},formatNum:e=>null==e?"--":Number(e).toLocaleString(),copyId(){navigator.clipboard?.writeText(this.nodeId).catch(()=>{})},changelogOpen:!1,changelogBusy:!1,changelogError:null,changes:[],changelogLimit:50,async loadChangelog(){this.changelogBusy=!0,this.changelogError=null;try{const e=new URLSearchParams({limit:this.changelogLimit}),t=await fetch(`${__ENV__.API}/system/changelog?${e}`),o=await t.json();if(!t.ok)throw new Error(o?.error?.message||"Failed");this.changes=o.data??[]}catch(e){this.changelogError=e?.message||String(e)}finally{this.changelogBusy=!1}},get changelogHasOlder(){return this.changes.length===this.changelogLimit},loadMoreChangelog(){this.changelogLimit=Math.min(this.changelogLimit+100,1e3),this.loadChangelog()},toggleChangelog(){this.changelogOpen=!this.changelogOpen,this.changelogOpen&&!this.changes.length&&this.loadChangelog()},actionColor:e=>({put:"var(--okdb-success)",remove:"var(--okdb-danger)",type_register:"var(--okdb-accent)",type_drop:"var(--okdb-danger)",index_register:"var(--okdb-purple)",index_drop:"var(--okdb-warning)"}[e]||"var(--okdb-app-fg-muted)"),actionLabel:e=>(e||"").replace(/_/g," ").toUpperCase(),shortKey:e=>e?e.length<=20?e:e.slice(0,10)+"…"+e.slice(-6):"—",relTime(e){if(!e)return"—";const t=Date.now()-e;return t<5e3?"just now":t<6e4?`${Math.round(t/1e3)}s ago`:t<36e5?`${Math.round(t/6e4)}m ago`:t<864e5?`${Math.round(t/36e5)}h ago`:new Date(e).toLocaleDateString()},showSystemEnvs:!1,get totalEnvSize(){return(this.envs||[]).reduce((e,t)=>e+(t.size||0),0)},get userEnvs(){return(this.envs||[]).filter(e=>!e.name.startsWith("~"))},get systemEnvs(){return(this.envs||[]).filter(e=>e.name.startsWith("~"))},get visibleEnvs(){return this.showSystemEnvs?this.envs:this.userEnvs},get orphanedEnvs(){return(this.envs||[]).filter(e=>e.orphaned)},goToEnv(t){e.$ok.stores.okdb.selectEnv(t),e.$vars.$router?.push?.("/db?env="+encodeURIComponent(t))},async openCreateEnv(){await e.$ok.shared.modal.open({owner:e,title:"Create Environment",content:'<create-env-modal :modal="modal" :on-done="onDone" />',context:t=>({modal:t,onDone:async()=>{await e.$ok.stores.okdb.reloadEnvs(),await this.load()}})})},async openDemoModal(){await e.$ok.shared.modal.open({owner:e,title:'<span style="background:linear-gradient(135deg,var(--okdb-accent),var(--okdb-purple) 50%,var(--okdb-success));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:700;font-size:15px;">✦ Demo Environment</span>',canDrag:!0,content:'<demo-env-modal :modal="modal" :on-done="onDone" />',context:t=>({modal:t,onDone:async t=>{e.$ok.stores.okdb.selectEnv(t),await e.$ok.stores.okdb.reloadEnvs(),await this.load(),e.$ok.shared?.toast?.success?.(`✦ Demo environment "${t}" is ready!`)}})})},_compactingEnvs:{},async compactEnv(t){if(!this._compactingEnvs[t]){this._compactingEnvs={...this._compactingEnvs,[t]:!0};try{const o=await fetch(`${__ENV__.API}/env/${encodeURIComponent(t)}/compact`,{method:"POST"}),s=await o.json();if(!o.ok)throw new Error(s?.error?.message||"Compact failed");const n=s.data??{},a=n.saved>0?`✓ Compacted "${t}": ${this.formatBytes(n.sizeBefore)} → ${this.formatBytes(n.sizeAfter)} (saved ${this.formatBytes(n.saved)}, ${n.savedPct}%)`:`✓ Compacted "${t}" — no space reclaimed (${this.formatBytes(n.sizeAfter)})`;e.$ok.shared?.toast?.success?.(a),await this.load()}catch(t){e.$ok.shared?.toast?.error?.("Compact failed: "+(t?.message||t))}finally{const e={...this._compactingEnvs};delete e[t],this._compactingEnvs=e}}},async openRemoveEnv(t){await e.$ok.shared.modal.open({owner:e,title:"Remove Environment",content:'<remove-env-modal :modal="modal" :env-name="envName" :on-done="onDone" />',context:o=>({modal:o,envName:t,onDone:async(t,o)=>{await e.$ok.stores.okdb.reloadEnvs(),e.$ok.stores.okdb.selectedEnv===t&&e.$ok.stores.okdb.selectEnv(e.$ok.stores.okdb._pickBestEnv()),await this.load(),o?.filesLocked?.length?e.$ok.shared?.toast?.warn?.(`"${t}" removed — some files could not be deleted (locked)`):e.$ok.shared?.toast?.success?.(`Environment "${t}" removed and files deleted`)}})})},async dismissOrphan(t){try{const o=await fetch(`${__ENV__.API}/env/${encodeURIComponent(t)}`,{method:"DELETE"}),s=await o.json().catch(()=>({}));if(!o.ok)throw new Error(s?.error?.message||o.statusText);const n=s.data??s;await e.$ok.stores.okdb.reloadEnvs(),await this.load(),n?.filesLocked?.length?e.$ok.shared?.toast?.warn?.(`Orphan "${t}" dismissed — some files could not be deleted (locked)`):e.$ok.shared?.toast?.info?.(`Orphan "${t}" dismissed`)}catch(t){e.$ok.shared?.toast?.error?.("Dismiss failed: "+(t?.message||t))}},async openDataOps(t){await e.$ok.shared.modal.open({owner:e,title:"⬦ Data Operations",canDrag:!0,content:'<data-ops-modal :modal="modal" :envs="envs" :on-done="onDone" :initial-tab="initialTab" />',context:o=>({modal:o,envs:this.envs,initialTab:t||"export",onDone:async()=>{await this.load(),e.$ok.shared?.toast?.success?.("Operation complete")}})})},logView:"live"}),async mount(e){await this.load(),this._timer=setInterval(()=>{this.autoRefresh&&"overview"===this.subView&&this.load()},1e4);const t=()=>{fetch(`${__ENV__.API}/envs`).then(e=>e.json()).then(t=>{this.envs=t.data??[],e.$ok.stores.okdb.envs=this.envs}).catch(()=>{})},o=e.$ok.stores.events?.on?.("env:opened",t),s=e.$ok.stores.events?.on?.("env:removed",t);this._unsubEnv=()=>{o?.(),s?.()},await this.loadDocsManifest()},unmount(){clearInterval(this._timer),this._unsubEnv?.()}}</script><template><component import="../../layouts/_default.ok.html"><template name="header"></template><template name="sidebar"><if :="subView === 'docs'"><nav class="sys-subnav" style="margin-bottom:6px"><button class="sys-subnav__item" @click="goToSystemSub('overview')" style="opacity:.6;font-size:11px">← System</button></nav><nav class="docs-nav" aria-label="Documentation"><each :of="docsManifest" let:item="item"><if :="item.file"><button class="docs-nav__item docs-nav__item--standalone" :class="{'docs-nav__item--active': docsIsActive(item.file)}" @click="docsNavigateTo(item.file)">{{ item.label }}</button></if><if :="item.children"><div class="docs-nav__group"><span class="docs-nav__group-label">{{ item.label }}</span><each :of="item.children" let:child="child"><button class="docs-nav__item" :class="{'docs-nav__item--active': docsIsActive(child.file)}" @click="docsNavigateTo(child.file)">{{ child.label }}</button></each></div></if></each></nav></if><if :="subView !== 'docs'"><nav class="sys-subnav" aria-label="System navigation"><button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='overview'}" @click="goToSystemSub('overview')"><span class="sys-subnav__icon">⚙</span> Overview</button> <button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='logs'}" @click="goToSystemSub('logs')"><span class="sys-subnav__icon">📋</span> Logs</button> <button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='events'}" @click="goToSystemSub('events')"><span class="sys-subnav__icon">⚡</span> Events</button><div class="sys-subnav__divider"></div><button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='sync'}" @click="goToSystemSub('sync')"><span class="sys-subnav__icon">⇄</span> Sync</button> <button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='auth'}" @click="goToSystemSub('auth')"><span class="sys-subnav__icon">🔐</span> Auth</button> <button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='license'}" @click="goToSystemSub('license')"><span class="sys-subnav__icon">🔑</span> License</button><div class="sys-subnav__divider"></div><button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='api'}" @click="goToSystemSub('api')"><span class="sys-subnav__icon">⌁</span> API</button> <button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='docs'}" @click="goToSystemSub('docs')"><span class="sys-subnav__icon">≡</span> Docs</button></nav></if></template><template name="default"><if :="subView === 'logs'"><div style="display:flex;flex-direction:column;gap:12px;height:100%;min-height:0"><div style="display:flex;align-items:center;gap:8px;flex-shrink:0"><div style="font-weight:600;font-size:15px;opacity:.8">Logs</div><div style="display:flex;gap:2px;padding:2px;background:var(--okdb-app-surface);border-radius:8px;border:1px solid var(--okdb-app-border)"><button @click="logView='live'" :style="logView==='live' ?'background:var(--okdb-app-surface-2);color:var(--okdb-accent);border-color:color-mix(in srgb, var(--okdb-accent) 27%, transparent);':'background:transparent;border-color:transparent;opacity:.7;'" style="padding:4px 14px;border-radius:6px;font-size:12px">Live</button> <button @click="logView='history'" :style="logView==='history' ?'background:var(--okdb-app-surface-2);color:var(--okdb-accent);border-color:color-mix(in srgb, var(--okdb-accent) 27%, transparent);':'background:transparent;border-color:transparent;opacity:.7;'" style="padding:4px 14px;border-radius:6px;font-size:12px">History</button></div></div><if :="logView === 'live'"><system-log :initial-source="'log'" style="flex:1;min-height:300px"/></if><if :="logView === 'history'"><system-log-history style="flex:1;min-height:300px"/></if></div></if><if :="subView === 'events'"><div style="display:flex;flex-direction:column;gap:12px;height:100%;min-height:0"><div style="font-weight:600;font-size:15px;opacity:.8;flex-shrink:0">Events</div><system-log :initial-source="'event'" style="flex:1;min-height:300px"/></div></if><if :="subView === 'sync'"><system-sync-panel style="height:100%;min-height:0"/></if><if :="subView === 'auth'"><system-auth-panel style="height:100%;min-height:0"/></if><if :="subView === 'api'"><system-api-panel style="height:100%;min-height:0"/></if><if :="subView === 'docs'"><div class="docs-wrap"><ok-doc-viewer :blocks="[]" root="/docs/" prefix="#/system/docs/" :file="docsCurrentFile" :anchor="docsAnchor" :manifest="docsManifest" sidebar-title="On this page" @anchor-change="docsOnAnchorChange($event.detail)"/></div></if><if :="subView === 'overview'"><div style="display:flex;flex-direction:column;gap:20px;max-width:900px"><div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap"><div style="font-weight:600;font-size:16px">Overview</div><button @click="load()" :disabled="loading" style="opacity:.8;font-size:12px">↻ Refresh</button> <label style="display:flex;align-items:center;gap:6px;font-size:12px;opacity:.7;cursor:pointer"><input type="checkbox" :checked="autoRefresh" @change="autoRefresh = $el.checked"/> Auto-refresh 10s</label><if :="loading"><span style="font-size:12px;opacity:.55">Loading…</span></if></div><if :="error"><div style="color:var(--okdb-danger);font-size:13px;padding:10px;border:1px solid var(--okdb-danger);border-radius:8px">{{error}}</div></if><div style="display:grid;grid-template-columns:1fr 1fr;gap:16px"><div class="card"><div class="card-title">Node Identity</div><div class="kv-grid"><div class="kv-key">Node ID</div><div class="kv-val" style="display:flex;align-items:center;gap:6px"><span style="font-family:monospace;font-size:12px;word-break:break-all">{{nodeId}}</span> <button @click="copyId()" style="font-size:10px;padding:2px 6px;opacity:.7">copy</button></div><if :="nodeAddress"><div class="kv-key">Address</div><div class="kv-val" style="font-family:monospace;font-size:12px">{{nodeAddress}}</div></if><div class="kv-key">Clock</div><div class="kv-val" style="font-family:monospace">{{formatNum(clock)}}</div><div class="kv-key">DB State</div><div class="kv-val" style="color:var(--okdb-success)">open</div><div class="kv-key">OKDB</div><div class="kv-val" style="font-family:monospace">v{{info?.versions?.okdb ?? info?.version ?? '--'}}</div><div class="kv-key">lmdb-js</div><div class="kv-val" style="font-family:monospace">v{{info?.versions?.lmdb ?? '--'}}</div><div class="kv-key">LMDB</div><div class="kv-val" style="font-family:monospace;font-size:11px;opacity:.7">{{info?.versions?.lmdbNative ?? '--'}}</div></div></div><div class="card"><div class="card-title">Storage (all environments)</div><div class="kv-grid"><div class="kv-key">Total Size</div><div class="kv-val" style="font-size:20px;font-weight:600;color:var(--okdb-accent)">{{approxSize}}</div><div class="kv-key">Environments</div><div class="kv-val">{{(envs || []).length}}</div><div class="kv-key">User Types</div><div class="kv-val">{{totalTypeCount}}</div><div class="kv-key">Page Size</div><div class="kv-val">{{formatNum(info?.pageSize)}} B</div><div class="kv-key">Map Size</div><div class="kv-val">{{info?.config?.lmdbOptions?.mapSize ? (info.config.lmdbOptions.mapSize/1024/1024/1024).toFixed(2)+' GB' : '--'}} <span style="opacity:.4;font-size:11px">(per env)</span></div></div></div></div><if :="envs && envs.length > 0"><div class="card"><div style="display:flex;align-items:center;gap:10px;margin-bottom:12px;flex-wrap:wrap"><div style="font-weight:600;font-size:14px;flex:1">Environments ({{userEnvs.length}}<if :="systemEnvs.length > 0"><span style="opacity:.4;font-size:12px;font-weight:400">+ {{systemEnvs.length}} system</span></if>)</div><span style="font-size:12px;opacity:.5">{{formatBytes(totalEnvSize)}}</span><if :="systemEnvs.length > 0"><button @click="showSystemEnvs = !showSystemEnvs" style="font-size:11px;padding:3px 8px;opacity:.6">{{showSystemEnvs ? '▴ hide system' : '⚙ show system'}}</button></if><button @click="openCreateEnv()" style="font-size:11px;padding:4px 10px;opacity:.8">+ Create</button> <button @click="openDemoModal()" style="font-size:11px;padding:4px 12px;background:linear-gradient(135deg,color-mix(in srgb,var(--okdb-accent) 7%,transparent),color-mix(in srgb,var(--okdb-purple) 7%,transparent));border:1px solid color-mix(in srgb,var(--okdb-accent) 27%,transparent);color:var(--okdb-accent);border-radius:6px;font-weight:600" title="Generate a demo environment">✦ Demo</button></div><table class="sys-table"><thead><tr><th>Name</th><th style="text-align:right">Size</th><th style="text-align:right">Clock</th><th style="text-align:right">Types</th><th style="text-align:center">Sync</th><th></th></tr></thead><tbody><tr as="repeat" :of="visibleEnvs" let:item="e" class="sys-table-row"><td style="font-family:monospace;font-size:12px"><a @click.prevent="goToEnv(e.name)" href="#" style="color:var(--okdb-accent);text-decoration:none" :title="'Explore '+e.name">{{e.name}}</a><if :="e.parentEnv"><span style="font-size:10px;opacity:.4;font-family:monospace;margin-left:4px">↳ {{e.parentEnv}}</span></if></td><td style="text-align:right;font-size:12px;font-family:monospace;opacity:.8">{{formatBytes(e.size)}}</td><td style="text-align:right;font-family:monospace;font-size:12px;opacity:.7">{{e.sync ? formatNum(e.clock) : '—'}}</td><td style="text-align:right;font-size:12px;opacity:.7">{{e.types ? e.types.length : 0}}</td><td style="text-align:center"><span :style="e.sync?'color:var(--okdb-success);font-size:11px;':'opacity:.4;font-size:11px;'">{{e.sync?'✓':'✗'}}</span></td><td style="text-align:right;white-space:nowrap"><button @click="compactEnv(e.name)" :disabled="_compactingEnvs[e.name]" :style="_compactingEnvs[e.name]?'font-size:10px;padding:2px 8px;opacity:.35;cursor:not-allowed;':'font-size:10px;padding:2px 8px;opacity:.55;'">{{_compactingEnvs[e.name]?'⏳ compacting…':'⬡ compact'}}</button><if :="e.name !== '~system'"><button @click="openRemoveEnv(e.name)" style="font-size:10px;padding:2px 8px;opacity:.45;color:var(--okdb-danger);border-color:color-mix(in srgb,var(--okdb-danger) 20%,transparent);margin-left:4px">🗑 remove</button></if></td></tr></tbody></table><if :="orphanedEnvs.length > 0"><div style="margin-top:16px;border-top:1px solid var(--okdb-app-border);padding-top:14px"><div style="font-size:12px;font-weight:600;opacity:.6;margin-bottom:8px;display:flex;align-items:center;gap:6px"><span style="color:var(--okdb-orange)">⚠</span> Orphaned sub-environments ({{orphanedEnvs.length}}) <span style="font-size:11px;font-weight:400;opacity:.55;margin-left:2px">— parent was removed; data on disk is intact</span></div><table class="sys-table"><tbody><tr as="repeat" :of="orphanedEnvs" let:item="e" class="sys-table-row"><td style="font-family:monospace;font-size:12px;opacity:.5;text-decoration:line-through">{{e.name}}</td><td style="font-size:11px;opacity:.4;font-style:italic">orphaned from <span style="font-family:monospace">{{e.orphanedFrom ?? '—'}}</span></td><td style="text-align:right;white-space:nowrap"><button @click="dismissOrphan(e.name)" style="font-size:10px;padding:2px 8px;opacity:.5;color:var(--okdb-danger);border-color:color-mix(in srgb,var(--okdb-danger) 20%,transparent)">✕ dismiss</button></td></tr></tbody></table></div></if></div></if><div class="card" style="border:1px solid color-mix(in srgb,var(--okdb-accent) 9%,transparent);background:linear-gradient(135deg,var(--okdb-app-bg-deep),var(--okdb-app-surface))"><div style="display:flex;align-items:center;gap:14px;flex-wrap:wrap"><div style="flex:1;min-width:200px"><div style="font-weight:600;font-size:14px;margin-bottom:4px;display:flex;align-items:center;gap:8px"><span style="color:var(--okdb-accent);opacity:.7">⬦</span> Data Operations</div><div style="font-size:12px;opacity:.45;line-height:1.5">Export & import logical NDJSON snapshots · hot filesystem backup</div></div><div style="display:flex;gap:8px;flex-shrink:0;flex-wrap:wrap;align-items:center"><button @click="openDataOps('export')" style="display:flex;align-items:center;gap:6px;padding:7px 15px;border-radius:8px;font-size:12px;font-weight:600;background:linear-gradient(135deg,var(--okdb-app-bg-deep),var(--okdb-app-bg-deep));border:1px solid color-mix(in srgb,var(--okdb-accent) 27%,transparent);color:var(--okdb-accent);cursor:pointer;white-space:nowrap">⬇ Export</button> <button @click="openDataOps('import')" style="display:flex;align-items:center;gap:6px;padding:7px 15px;border-radius:8px;font-size:12px;font-weight:600;background:linear-gradient(135deg,var(--okdb-app-bg-deep),var(--okdb-app-bg-deep));border:1px solid color-mix(in srgb,var(--okdb-accent) 27%,transparent);color:var(--okdb-accent);cursor:pointer;white-space:nowrap">⬆ Import</button><div style="width:1px;height:24px;background:var(--okdb-app-border);margin:0 2px;flex-shrink:0"></div><button @click="openDataOps('backup')" style="display:flex;align-items:center;gap:6px;padding:7px 15px;border-radius:8px;font-size:12px;font-weight:600;background:linear-gradient(135deg,var(--okdb-app-bg-deep),var(--okdb-app-bg-deep));border:1px solid color-mix(in srgb,var(--okdb-purple) 27%,transparent);color:var(--okdb-purple);cursor:pointer;white-space:nowrap">🗄 Backup</button></div></div></div><div style="display:grid;grid-template-columns:1fr 1fr;gap:16px"><div class="card"><div class="card-title">Configuration</div><div class="kv-grid"><div class="kv-key">Durability</div><div class="kv-val" style="display:flex;align-items:center;gap:6px"><span :style="'font-size:11px;font-weight:600;padding:2px 8px;border-radius:999px;border:1px solid;color:'+durabilityLabel.color+';border-color:'+durabilityLabel.color+'44;background:'+durabilityLabel.color+'11;'">{{durabilityLabel.label}}</span> <span style="opacity:.5;font-size:11px;flex:1">{{durabilityLabel.hint}}</span></div><div class="kv-key">Compression</div><div class="kv-val"><span :style="config?.lmdbOptions?.compression?'color:var(--okdb-success)':'opacity:.5'">{{config?.lmdbOptions?.compression?'LZ4 enabled':'disabled'}}</span></div><div class="kv-key">Encryption</div><div class="kv-val"><span :style="config?.lmdbOptions?.encryptionKey?'color:var(--okdb-success)':'opacity:.5'">{{config?.lmdbOptions?.encryptionKey?'Chacha8 enabled':'none'}}</span></div><div class="kv-key">Map Size</div><div class="kv-val">{{formatBytes(config?.lmdbOptions?.mapSize)}}</div><div class="kv-key">Max DBs</div><div class="kv-val">{{formatNum(config?.lmdbOptions?.maxDbs)}}</div></div><if :="configWarnings.length > 0"><div style="margin-top:10px;display:flex;flex-direction:column;gap:4px"><for :="configWarnings" let:w><div style="font-size:11px;color:var(--okdb-warning);background:color-mix(in srgb,var(--okdb-warning) 7%,transparent);border:1px solid color-mix(in srgb,var(--okdb-warning) 20%,transparent);border-radius:6px;padding:5px 8px;line-height:1.4">⚠ {{w}}</div></for></div></if></div><div class="card"><div class="card-title">Process</div><if :="!runtime"><div style="opacity:.4;font-size:12px">Loading…</div></if><if :="runtime"><div class="kv-grid"><div class="kv-key">PID</div><div class="kv-val" style="font-family:monospace">{{runtime.process?.pid}}</div><div class="kv-key">Uptime</div><div class="kv-val">{{formatUptime(runtime.process?.uptime)}}</div><div class="kv-key">Node.js</div><div class="kv-val" style="font-family:monospace">v{{runtime.process?.node}}</div><div class="kv-key" style="align-self:center">CPU</div><div class="kv-val" style="display:flex;align-items:center;gap:6px"><div style="flex:1;height:6px;background:var(--okdb-app-border);border-radius:3px;overflow:hidden"><div :style="'height:100%;border-radius:3px;transition:width .4s;background:'+(runtime.process?.cpu?.pct>80?'var(--okdb-danger)':runtime.process?.cpu?.pct>40?'var(--okdb-warning)':'var(--okdb-success)')+';width:'+(runtime.process?.cpu?.pct??0)+'%;'"></div></div><span style="font-size:11px;opacity:.6;min-width:30px">{{runtime.process?.cpu?.pct??0}}%</span></div><div class="kv-key">RSS</div><div class="kv-val">{{formatBytes(runtime.process?.memory?.rss)}}</div><div class="kv-key">Heap Used</div><div class="kv-val">{{formatBytes(runtime.process?.memory?.heapUsed)}} <span style="opacity:.45;font-size:11px">/ {{formatBytes(runtime.process?.memory?.heapTotal)}}</span></div><div class="kv-key" style="align-self:center">Heap %</div><div class="kv-val" style="display:flex;align-items:center;gap:6px"><div style="flex:1;height:6px;background:var(--okdb-app-border);border-radius:3px;overflow:hidden"><div :style="'height:100%;background:var(--okdb-accent);border-radius:3px;width:'+pct(runtime.process?.memory?.heapUsed,runtime.process?.memory?.heapTotal)+'%;'"></div></div><span style="font-size:11px;opacity:.6;min-width:30px">{{pct(runtime.process?.memory?.heapUsed,runtime.process?.memory?.heapTotal)}}%</span></div><div class="kv-key">External</div><div class="kv-val">{{formatBytes(runtime.process?.memory?.external)}}</div></div></if></div></div><div class="card"><div class="card-title">Machine</div><if :="!runtime"><div style="opacity:.4;font-size:12px">Loading…</div></if><if :="runtime"><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px"><div><div style="font-size:11px;opacity:.5;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">Memory</div><div style="font-size:22px;font-weight:600;color:var(--okdb-accent)">{{formatBytes(runtime.os?.freeMemory)}}</div><div style="font-size:11px;opacity:.5;margin-top:2px">free of {{formatBytes(runtime.os?.totalMemory)}}</div><div style="margin-top:8px;height:6px;background:var(--okdb-app-border);border-radius:3px;overflow:hidden"><div :style="'height:100%;background:var(--okdb-accent);border-radius:3px;width:'+pct(runtime.os?.totalMemory-runtime.os?.freeMemory,runtime.os?.totalMemory)+'%;'"></div></div><div style="font-size:11px;opacity:.45;margin-top:3px">{{pct(runtime.os?.totalMemory-runtime.os?.freeMemory,runtime.os?.totalMemory)}}% used</div></div><div><div style="font-size:11px;opacity:.5;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">CPU</div><div style="font-size:14px;font-weight:600">{{runtime.os?.cpuCount}} core{{runtime.os?.cpuCount!==1?'s':''}}</div><div style="font-size:11px;opacity:.5;margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis" :title="runtime.os?.cpuModel">{{runtime.os?.cpuModel}}</div><div style="margin-top:8px;font-size:11px;opacity:.6">Load avg (1m/5m/15m)</div><div style="font-size:13px;font-family:monospace;margin-top:2px">{{formatLoad(runtime.os?.loadAvg)}}</div></div><div><div style="font-size:11px;opacity:.5;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">Platform</div><div style="font-size:14px;font-weight:600;text-transform:capitalize">{{runtime.os?.platform}}</div><div style="font-size:11px;opacity:.5;margin-top:3px">{{runtime.os?.arch}}</div><div style="font-size:11px;opacity:.35;margin-top:6px;word-break:break-all">{{runtime.os?.release}}</div><div style="font-size:11px;opacity:.4;margin-top:3px;font-family:monospace">{{runtime.os?.hostname}}</div></div></div></if></div><if :="metrics"><div class="card"><div class="card-title">API Metrics</div><div style="display:grid;grid-template-columns:1fr 1fr;gap:16px"><div><div style="font-size:11px;opacity:.5;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">Lifetime</div><div class="kv-grid"><div class="kv-key">Requests</div><div class="kv-val" style="color:var(--okdb-accent);font-weight:600">{{formatNum(metrics.lifetime?.requests)}}</div><div class="kv-key">Reads</div><div class="kv-val">{{formatNum(metrics.lifetime?.reads)}}</div><div class="kv-key">Writes</div><div class="kv-val">{{formatNum(metrics.lifetime?.writes)}}</div><div class="kv-key">Errors</div><div class="kv-val" :style="metrics.lifetime?.errors>0?'color:var(--okdb-danger);':'opacity:.5;'">{{formatNum(metrics.lifetime?.errors)}}</div><div class="kv-key">Avg Read</div><div class="kv-val" style="font-family:monospace">{{metrics.lifetime?.avgReadMs!=null?metrics.lifetime.avgReadMs+'ms':'—'}}</div><div class="kv-key">Avg Write</div><div class="kv-val" style="font-family:monospace">{{metrics.lifetime?.avgWriteMs!=null?metrics.lifetime.avgWriteMs+'ms':'—'}}</div></div></div><div><div style="font-size:11px;opacity:.5;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">Last 60 seconds</div><div class="kv-grid"><div class="kv-key">Req/s</div><div class="kv-val" style="color:var(--okdb-success);font-weight:600">{{metrics.window?.reqPerSec}}</div><div class="kv-key">Read/s</div><div class="kv-val">{{metrics.window?.readPerSec}}</div><div class="kv-key">Write/s</div><div class="kv-val">{{metrics.window?.writePerSec}}</div><div class="kv-key">Requests</div><div class="kv-val">{{formatNum(metrics.window?.requests)}}</div><div class="kv-key">Avg Read</div><div class="kv-val" style="font-family:monospace">{{metrics.window?.avgReadMs!=null?metrics.window.avgReadMs+'ms':'—'}}</div><div class="kv-key">Avg Write</div><div class="kv-val" style="font-family:monospace">{{metrics.window?.avgWriteMs!=null?metrics.window.avgWriteMs+'ms':'—'}}</div></div></div></div></div></if><if :="plugins.length > 0"><div class="card"><div class="card-title">Plugins ({{plugins.length}})</div><div style="display:flex;flex-wrap:wrap;gap:8px"><for :="plugins" let:p><span style="padding:4px 12px;border-radius:999px;font-size:12px;border:1px solid color-mix(in srgb,var(--okdb-accent) 33%,transparent);color:var(--okdb-accent);background:color-mix(in srgb,var(--okdb-accent) 7%,transparent)">{{p}}</span></for></div></div></if><if :="typeList.length > 0"><div class="card"><div class="card-title">Types ({{typeList.length}} across all environments)</div><table class="sys-table"><thead><tr><th>Type</th><th>Environment</th><th style="text-align:right">Records</th></tr></thead><tbody><tr as="repeat" :of="typeList" let:item="entry"><td style="font-family:monospace;font-size:12px">{{entry.name}}</td><td style="font-family:monospace;font-size:11px;opacity:.55">{{entry.env}}</td><td style="text-align:right;color:var(--okdb-accent)">{{formatNum(entry.count)}}</td></tr></tbody></table></div></if><if :="systemTypeList.length > 0"><div class="card"><details><summary style="cursor:pointer;font-weight:600;font-size:13px;list-style:none;display:flex;align-items:center;gap:8px"><span>▶</span><span>Internal / System Types ({{systemTypeList.length}})</span></summary><table class="sys-table" style="margin-top:10px"><thead><tr><th>Type</th><th>Environment</th><th style="text-align:right">Records</th></tr></thead><tbody><tr as="repeat" :of="systemTypeList" let:item="entry"><td style="font-family:monospace;font-size:12px;opacity:.7">{{entry.name}}</td><td style="font-family:monospace;font-size:11px;opacity:.45">{{entry.env}}</td><td style="text-align:right;color:var(--okdb-accent);opacity:.7">{{formatNum(entry.count)}}</td></tr></tbody></table></details></div></if><div class="card"><div style="display:flex;align-items:center;gap:10px;cursor:pointer" @click="toggleChangelog()"><div style="font-weight:600;font-size:14px;flex:1">System Change Log</div><span style="font-size:12px;opacity:.5">all environments</span> <span style="opacity:.5;font-size:12px">{{changelogOpen ? '▲' : '▼'}}</span></div><if :="changelogOpen"><div style="margin-top:12px;display:flex;flex-direction:column;gap:8px"><if :="changelogError"><div style="color:var(--okdb-danger);font-size:12px">{{changelogError}}</div></if><div style="display:flex;gap:6px;align-items:center"><button @click="loadChangelog()" :disabled="changelogBusy" style="font-size:12px;opacity:.8">↻ Refresh</button> <span style="font-size:12px;opacity:.5;margin-left:auto">showing {{changes.length}} entries</span></div><div style="overflow:auto;max-height:400px"><table style="width:100%;border-collapse:collapse;font-size:12px"><thead style="position:sticky;top:0;background:var(--okdb-app-surface);z-index:1"><tr><th class="cl-th" style="width:80px">Env</th><th class="cl-th" style="width:110px">Action</th><th class="cl-th" style="width:80px">Type</th><th class="cl-th">Key</th><th class="cl-th" style="width:90px;text-align:right">When</th></tr></thead><tbody><tr as="repeat" :of="changes" let:item="ch" class="cl-row"><td class="cl-td" style="font-family:monospace;font-size:10px;opacity:.5;white-space:nowrap;max-width:80px;overflow:hidden;text-overflow:ellipsis" :title="ch._env">{{ch._env||'default'}}</td><td class="cl-td"><span :style="'font-size:10px;padding:2px 5px;border-radius:3px;border:1px solid;color:'+actionColor(ch.action)+';border-color:'+actionColor(ch.action)+'44;'">{{actionLabel(ch.action)}}</span></td><td class="cl-td" style="font-family:monospace;font-size:11px;opacity:.75;max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" :title="ch.type">{{ch.type||'—'}}</td><td class="cl-td" style="font-family:monospace;font-size:11px;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" :title="ch.key">{{shortKey(ch.key)}}</td><td class="cl-td" style="text-align:right;opacity:.55;white-space:nowrap;font-size:11px">{{relTime(ch.timestamp)}}</td></tr></tbody></table></div><if :="changelogHasOlder"><button @click="loadMoreChangelog()" :disabled="changelogBusy" style="font-size:12px;align-self:flex-end">Load more ▼ ({{changelogLimit+100}})</button></if></div></if></div></div></if><if :="subView === 'license'"><license-panel/></if></template></component></template><style>.sys-subnav{display:flex;flex-direction:column;gap:2px}.sys-subnav__item{display:flex;align-items:center;gap:6px;width:100%;text-align:left;padding:6px 10px;border-radius:7px;background:0 0;border-color:transparent;color:var(--okdb-app-fg-muted);font-size:12px;cursor:pointer;transition:color .13s,background .13s,border-color .13s;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sys-subnav__item:hover{background:var(--okdb-app-surface-2);border-color:var(--okdb-app-border);color:var(--okdb-app-fg)}.sys-subnav__item--active{background:var(--okdb-app-bg-deep)!important;border-color:color-mix(in srgb,var(--okdb-accent) 27%,transparent)!important;color:var(--okdb-accent)!important}.sys-subnav__icon{font-size:12px;opacity:.7;flex-shrink:0}.sys-subnav__item--active .sys-subnav__icon{opacity:1}.sys-subnav__divider{height:1px;background:var(--okdb-app-surface);margin:3px 2px}.docs-nav{display:flex;flex-direction:column;gap:1px;margin:0 -10px;padding:2px 0}.docs-nav__item{display:block;width:100%;text-align:left;padding:5px 14px;border-radius:0;border:none;border-left:2px solid transparent;background:0 0;color:var(--okdb-app-fg-muted);font-size:12.5px;cursor:pointer;transition:color .13s,background .13s,border-color .13s;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.55;letter-spacing:.01em}.docs-nav__item--standalone{font-weight:500;color:var(--okdb-app-fg);margin-top:2px}.docs-nav__item:hover{background:var(--okdb-app-surface-2);color:var(--okdb-app-fg);border-left-color:var(--okdb-app-border-strong)}.docs-nav__item--active{background:var(--okdb-accent-soft-bg)!important;color:var(--okdb-accent)!important;border-left-color:var(--okdb-accent)!important;font-weight:500}.docs-nav__group{margin-top:6px;padding-top:4px;border-top:1px solid var(--okdb-app-surface)}.docs-nav__group-label{display:block;font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--okdb-app-fg-faint);padding:4px 14px 5px;pointer-events:none;user-select:none}.docs-wrap{margin:-20px -24px -6px;min-height:calc(100% + 26px)}.docs-wrap ok-doc-viewer{--ok_color-accent:var(--okdb-accent);--ok_color-fg:var(--okdb-app-fg);--ok_color-muted-fg:var(--okdb-app-fg-muted);--ok_color-muted-border:var(--okdb-app-border);--font-mono:'Cascadia Code','Fira Code','Consolas',monospace;display:block;min-height:100%}.docs-wrap ok-doc-viewer .md table{border-collapse:collapse;width:100%;margin:1.2rem 0;font-size:13px}.docs-wrap ok-doc-viewer .md td,.docs-wrap ok-doc-viewer .md th{border:1px solid var(--okdb-app-border);padding:7px 12px;text-align:left}.docs-wrap ok-doc-viewer .md th{background:var(--okdb-app-surface-2);color:#8fc8e8;font-weight:600;font-size:12px;letter-spacing:.03em}.docs-wrap ok-doc-viewer .md tr:hover td{background:var(--okdb-app-surface)}.docs-wrap ok-doc-viewer .md a{color:var(--okdb-accent);text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--okdb-accent) 27%,transparent);transition:border-color .15s,color .15s}.docs-wrap ok-doc-viewer .md a:hover{color:#7fd8ff;border-bottom-color:#7fd8ff88}.docs-wrap ok-doc-viewer .md code:not(pre code){background:var(--okdb-app-bg-deep);border:1px solid var(--okdb-app-border);color:var(--okdb-accent);padding:1px 6px;border-radius:4px;font-size:.9em}.docs-wrap ok-doc-viewer .md blockquote{border-left:3px solid color-mix(in srgb,var(--okdb-accent) 27%,transparent);margin:1rem 0;padding:6px 14px;background:var(--okdb-app-surface);border-radius:0 6px 6px 0;color:var(--okdb-app-fg-muted);font-style:italic}.card{background:var(--okdb-app-surface);border-radius:12px;padding:16px;box-shadow:0 0 5px #ffffff1a}.card-title{font-weight:600;margin-bottom:12px;font-size:14px}.kv-grid{display:grid;grid-template-columns:110px 1fr;gap:6px 12px;font-size:13px}.kv-key{opacity:.6}.kv-val{word-break:break-all}.sys-table{width:100%;border-collapse:collapse;font-size:13px}.sys-table th{text-align:left;padding:6px 8px;border-bottom:1px solid var(--okdb-app-border);opacity:.6;font-weight:500;font-size:11px;text-transform:uppercase;letter-spacing:.04em}.sys-table td{padding:5px 8px;border-bottom:1px solid var(--okdb-app-border)}.sys-table tr:last-child td{border-bottom:none}.sys-table tr:hover td{background:#ffffff08}.cl-th{padding:5px 8px;text-align:left;font-weight:500;font-size:10px;text-transform:uppercase;letter-spacing:.04em;opacity:.5;border-bottom:1px solid var(--okdb-app-border)}.cl-td{padding:5px 8px;border-bottom:1px solid var(--okdb-app-border)}.cl-row:hover .cl-td{background:#ffffff06}</style>
|
|
1
|
+
<script type="module">import e from"../db/modals/create-env-modal.ok.js";import t from"../db/modals/demo-env-modal.ok.js";import o from"../db/modals/remove-env-modal.ok.js";import s from"./modals/data-ops-modal.ok.js";import n from"./parts/system-log.ok.js";import a from"./parts/system-log-history.ok.js";import r from"./parts/system-sync-panel.ok.js";import i from"./parts/system-auth-panel.ok.js";import c from"./parts/system-api-panel.ok.js";import l from"../license/parts/license-panel.ok.js";import d from"@toolkit/components/ok-doc-viewer.ok.js";export default{tag:"system-index",unwrap:!0,dependencies:[n,a,r,i,c,l,d,e,t,o,s],context:e=>({_systemWildcard(){const t=e.$vars.$router?.match("/system/*");return t?.params?.["*"]??""},get subView(){return this._parseSubView(this._systemWildcard())},_parseSubView(e){const t=e??"";return t&&"overview"!==t?"logs"===t?"logs":"events"===t?"events":"sync"===t?"sync":"api"===t?"api":"auth"===t?"auth":"license"===t?"license":t.startsWith("docs")?"docs":"overview":"overview"},goToSystemSub(t){const o=e.$vars.$router;if(!o)return;const s=e.$ok.stores.okdb.selectedEnv,n=s?`?env=${encodeURIComponent(s)}`:"";o.push(`/system/${t}${n}`)},docsManifest:[],docsAnchor:"",get docsCurrentFile(){return this._parseDocsFile(this._systemWildcard())},_parseDocsFile(e){const t=e??"",o=t.startsWith("docs/")?t.slice(5):"";return o?o.replace(/\.md$/,"")+".md":"index.md"},docsIsActive(e){return this.docsCurrentFile===e},docsNavigateTo(e){const t=e.replace(/\.md$/,"");this.docsAnchor="",this.goToSystemSub("docs/"+t)},docsOnAnchorChange(e){this.docsAnchor=e},async loadDocsManifest(){if(!this.docsManifest.length)try{const e=await fetch("/docs/manifest.json");e.ok&&(this.docsManifest=await e.json())}catch{}},info:null,runtime:null,syncInfo:null,envs:[],loading:!1,error:null,autoRefresh:!0,_timer:null,metrics:null,async load(){this.loading=!0,this.error=null;try{const e=await fetch(`${__ENV__.API}/info`),t=await e.json();if(!e.ok)throw new Error(t?.error?.message||"Failed to load info");this.info=t.data??t}catch(e){this.error=e?.message||String(e)}finally{this.loading=!1}try{const t=await fetch(`${__ENV__.API}/envs`);if(t.ok){const o=await t.json();this.envs=o.data??[],e.$ok.stores.okdb.envs=this.envs}}catch{}try{const e=await fetch(`${__ENV__.API}/system/runtime`);if(e.ok){const t=await e.json();this.runtime=t.data??t}}catch{}try{const e=await fetch(`${__ENV__.API}/system/metrics`);if(e.ok){const t=await e.json();this.metrics=t.data??null}}catch{}try{const e=await fetch(`${__ENV__.API}/sync/info`);this.syncInfo=e.ok?(await e.json()).data??null:null}catch{this.syncInfo=null}},get approxSize(){const e=(this.envs||[]).reduce((e,t)=>e+(t.size||0),0);if(!e)return"--";const t=["B","KB","MB","GB","TB"];let o=e,s=0;for(;o>=1024&&s<t.length-1;)o/=1024,s++;return o.toFixed(2)+" "+t[s]},get typeList(){const e=[];for(const t of this.envs||[])for(const o of t.types||[])o.name.startsWith("~")||e.push({name:o.name,env:t.name,count:o.count??0});return e.sort((e,t)=>e.name.localeCompare(t.name)||e.env.localeCompare(t.env))},get systemTypeList(){const e=[];for(const t of this.envs||[])for(const o of t.types||[])o.name.startsWith("~")&&e.push({name:o.name,env:t.name,count:o.count??0});return e.sort((e,t)=>e.name.localeCompare(t.name)||e.env.localeCompare(t.env))},get totalTypeCount(){return this.typeList.length},get plugins(){return(this.info?.plugins||[]).filter(Boolean)},get config(){return this.info?.config??null},get durabilityLabel(){const e=this.config?.durability;return{strict:{label:"Strict",color:"var(--okdb-success)",hint:"Full fsync on every commit."},balanced:{label:"Balanced",color:"var(--okdb-accent)",hint:"Async fsync worker — crash-safe, fast."},fast:{label:"Fast",color:"var(--okdb-warning)",hint:"No per-commit fsync. May lose last tx on crash."},custom:{label:"Custom",color:"var(--okdb-purple)",hint:"Manual LMDB flags. No preset applied."}}[e]??{label:e??"--",color:"var(--okdb-app-fg-muted)",hint:""}},get configWarnings(){return this.config?.warnings??[]},formatBytes(e){if(null==e)return"--";const t=["B","KB","MB","GB","TB"];let o=e,s=0;for(;o>=1024&&s<t.length-1;)o/=1024,s++;return o.toFixed(0===s?0:2)+" "+t[s]},formatUptime(e){if(null==e)return"--";const t=Math.floor(e),o=Math.floor(t/86400),s=Math.floor(t%86400/3600),n=Math.floor(t%3600/60),a=t%60;return o>0?`${o}d ${s}h ${n}m`:s>0?`${s}h ${n}m ${a}s`:n>0?`${n}m ${a}s`:`${a}s`},formatLoad:e=>e&&e.length?e.map(e=>e.toFixed(2)).join(" / "):"--",pct:(e,t)=>e&&t?Math.round(e/t*100):0,get clock(){return this.syncInfo?.clock??this.info?.clock??"--"},get nodeId(){return this.syncInfo?.node?.id??this.info?.id??"--"},get nodeAddress(){return this.syncInfo?.node?.address??null},formatNum:e=>null==e?"--":Number(e).toLocaleString(),copyId(){navigator.clipboard?.writeText(this.nodeId).catch(()=>{})},changelogOpen:!1,changelogBusy:!1,changelogError:null,changes:[],changelogLimit:50,async loadChangelog(){this.changelogBusy=!0,this.changelogError=null;try{const e=new URLSearchParams({limit:this.changelogLimit}),t=await fetch(`${__ENV__.API}/system/changelog?${e}`),o=await t.json();if(!t.ok)throw new Error(o?.error?.message||"Failed");this.changes=o.data??[]}catch(e){this.changelogError=e?.message||String(e)}finally{this.changelogBusy=!1}},get changelogHasOlder(){return this.changes.length===this.changelogLimit},loadMoreChangelog(){this.changelogLimit=Math.min(this.changelogLimit+100,1e3),this.loadChangelog()},toggleChangelog(){this.changelogOpen=!this.changelogOpen,this.changelogOpen&&!this.changes.length&&this.loadChangelog()},actionColor:e=>({put:"var(--okdb-success)",remove:"var(--okdb-danger)",type_register:"var(--okdb-accent)",type_drop:"var(--okdb-danger)",index_register:"var(--okdb-purple)",index_drop:"var(--okdb-warning)"}[e]||"var(--okdb-app-fg-muted)"),actionLabel:e=>(e||"").replace(/_/g," ").toUpperCase(),shortKey:e=>e?e.length<=20?e:e.slice(0,10)+"…"+e.slice(-6):"—",relTime(e){if(!e)return"—";const t=Date.now()-e;return t<5e3?"just now":t<6e4?`${Math.round(t/1e3)}s ago`:t<36e5?`${Math.round(t/6e4)}m ago`:t<864e5?`${Math.round(t/36e5)}h ago`:new Date(e).toLocaleDateString()},showSystemEnvs:!1,get totalEnvSize(){return(this.envs||[]).reduce((e,t)=>e+(t.size||0),0)},get userEnvs(){return(this.envs||[]).filter(e=>!e.name.startsWith("~"))},get systemEnvs(){return(this.envs||[]).filter(e=>e.name.startsWith("~"))},get visibleEnvs(){return this.showSystemEnvs?this.envs:this.userEnvs},get orphanedEnvs(){return(this.envs||[]).filter(e=>e.orphaned)},goToEnv(t){e.$ok.stores.okdb.selectEnv(t),e.$vars.$router?.push?.("/db?env="+encodeURIComponent(t))},async openCreateEnv(){await e.$ok.shared.modal.open({owner:e,title:"Create Environment",content:'<create-env-modal :modal="modal" :on-done="onDone" />',context:t=>({modal:t,onDone:async()=>{await e.$ok.stores.okdb.reloadEnvs(),await this.load()}})})},async openDemoModal(){await e.$ok.shared.modal.open({owner:e,title:'<span style="background:linear-gradient(135deg,var(--okdb-accent),var(--okdb-purple) 50%,var(--okdb-success));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:700;font-size:15px;">✦ Demo Environment</span>',canDrag:!0,content:'<demo-env-modal :modal="modal" :on-done="onDone" />',context:t=>({modal:t,onDone:async t=>{e.$ok.stores.okdb.selectEnv(t),await e.$ok.stores.okdb.reloadEnvs(),await this.load(),e.$ok.shared?.toast?.success?.(`✦ Demo environment "${t}" is ready!`)}})})},_compactingEnvs:{},async compactEnv(t){if(!this._compactingEnvs[t]){this._compactingEnvs={...this._compactingEnvs,[t]:!0};try{const o=await fetch(`${__ENV__.API}/env/${encodeURIComponent(t)}/compact`,{method:"POST"}),s=await o.json();if(!o.ok)throw new Error(s?.error?.message||"Compact failed");const n=s.data??{},a=n.saved>0?`✓ Compacted "${t}": ${this.formatBytes(n.sizeBefore)} → ${this.formatBytes(n.sizeAfter)} (saved ${this.formatBytes(n.saved)}, ${n.savedPct}%)`:`✓ Compacted "${t}" — no space reclaimed (${this.formatBytes(n.sizeAfter)})`;e.$ok.shared?.toast?.success?.(a),await this.load()}catch(t){e.$ok.shared?.toast?.error?.("Compact failed: "+(t?.message||t))}finally{const e={...this._compactingEnvs};delete e[t],this._compactingEnvs=e}}},async openRemoveEnv(t){await e.$ok.shared.modal.open({owner:e,title:"Remove Environment",content:'<remove-env-modal :modal="modal" :env-name="envName" :on-done="onDone" />',context:o=>({modal:o,envName:t,onDone:async(t,o)=>{await e.$ok.stores.okdb.reloadEnvs(),e.$ok.stores.okdb.selectedEnv===t&&e.$ok.stores.okdb.selectEnv(e.$ok.stores.okdb._pickBestEnv()),await this.load(),o?.filesLocked?.length?e.$ok.shared?.toast?.warn?.(`"${t}" removed — some files could not be deleted (locked)`):e.$ok.shared?.toast?.success?.(`Environment "${t}" removed and files deleted`)}})})},async dismissOrphan(t){try{const o=await fetch(`${__ENV__.API}/env/${encodeURIComponent(t)}`,{method:"DELETE"}),s=await o.json().catch(()=>({}));if(!o.ok)throw new Error(s?.error?.message||o.statusText);const n=s.data??s;await e.$ok.stores.okdb.reloadEnvs(),await this.load(),n?.filesLocked?.length?e.$ok.shared?.toast?.warn?.(`Orphan "${t}" dismissed — some files could not be deleted (locked)`):e.$ok.shared?.toast?.info?.(`Orphan "${t}" dismissed`)}catch(t){e.$ok.shared?.toast?.error?.("Dismiss failed: "+(t?.message||t))}},async openDataOps(t){await e.$ok.shared.modal.open({owner:e,title:"⬦ Data Operations",canDrag:!0,content:'<data-ops-modal :modal="modal" :envs="envs" :on-done="onDone" :initial-tab="initialTab" />',context:o=>({modal:o,envs:this.envs,initialTab:t||"export",onDone:async()=>{await this.load(),e.$ok.shared?.toast?.success?.("Operation complete")}})})},logView:"live"}),async mount(e){await this.load(),this._timer=setInterval(()=>{this.autoRefresh&&"overview"===this.subView&&this.load()},1e4);const t=()=>{fetch(`${__ENV__.API}/envs`).then(e=>e.json()).then(t=>{this.envs=t.data??[],e.$ok.stores.okdb.envs=this.envs}).catch(()=>{})},o=e.$ok.stores.events?.on?.("env:opened",t),s=e.$ok.stores.events?.on?.("env:removed",t);this._unsubEnv=()=>{o?.(),s?.()},await this.loadDocsManifest()},unmount(){clearInterval(this._timer),this._unsubEnv?.()}};</script><template><component import="../../layouts/_default.ok.html"><template name="header"></template><template name="sidebar"><if :="subView === 'docs'"><nav class="sys-subnav" style="margin-bottom:6px"><button class="sys-subnav__item" @click="goToSystemSub('overview')" style="opacity:.6;font-size:11px">← System</button></nav><nav class="docs-nav" aria-label="Documentation"><each :of="docsManifest" let:item="item"><if :="item.file"><button class="docs-nav__item docs-nav__item--standalone" :class="{'docs-nav__item--active': docsIsActive(item.file)}" @click="docsNavigateTo(item.file)">{{ item.label }}</button></if><if :="item.children"><div class="docs-nav__group"><span class="docs-nav__group-label">{{ item.label }}</span><each :of="item.children" let:child="child"><button class="docs-nav__item" :class="{'docs-nav__item--active': docsIsActive(child.file)}" @click="docsNavigateTo(child.file)">{{ child.label }}</button></each></div></if></each></nav></if><if :="subView !== 'docs'"><nav class="sys-subnav" aria-label="System navigation"><button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='overview'}" @click="goToSystemSub('overview')"><span class="sys-subnav__icon">⚙</span> Overview</button> <button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='logs'}" @click="goToSystemSub('logs')"><span class="sys-subnav__icon">📋</span> Logs</button> <button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='events'}" @click="goToSystemSub('events')"><span class="sys-subnav__icon">⚡</span> Events</button><div class="sys-subnav__divider"></div><button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='sync'}" @click="goToSystemSub('sync')"><span class="sys-subnav__icon">⇄</span> Sync</button> <button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='auth'}" @click="goToSystemSub('auth')"><span class="sys-subnav__icon">🔐</span> Auth</button> <button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='license'}" @click="goToSystemSub('license')"><span class="sys-subnav__icon">🔑</span> License</button><div class="sys-subnav__divider"></div><button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='api'}" @click="goToSystemSub('api')"><span class="sys-subnav__icon">⌁</span> API</button> <button class="sys-subnav__item" :class="{'sys-subnav__item--active': subView==='docs'}" @click="goToSystemSub('docs')"><span class="sys-subnav__icon">≡</span> Docs</button></nav></if></template><template name="default"><if :="subView === 'logs'"><div style="display:flex;flex-direction:column;gap:12px;height:100%;min-height:0"><div style="display:flex;align-items:center;gap:8px;flex-shrink:0"><div style="font-weight:600;font-size:15px;opacity:.8">Logs</div><div style="display:flex;gap:2px;padding:2px;background:var(--okdb-app-surface);border-radius:8px;border:1px solid var(--okdb-app-border)"><button @click="logView='live'" :style="logView==='live' ?'background:var(--okdb-app-surface-2);color:var(--okdb-accent);border-color:color-mix(in srgb, var(--okdb-accent) 27%, transparent);':'background:transparent;border-color:transparent;opacity:.7;'" style="padding:4px 14px;border-radius:6px;font-size:12px">Live</button> <button @click="logView='history'" :style="logView==='history' ?'background:var(--okdb-app-surface-2);color:var(--okdb-accent);border-color:color-mix(in srgb, var(--okdb-accent) 27%, transparent);':'background:transparent;border-color:transparent;opacity:.7;'" style="padding:4px 14px;border-radius:6px;font-size:12px">History</button></div></div><if :="logView === 'live'"><system-log :initial-source="'log'" style="flex:1;min-height:300px"/></if><if :="logView === 'history'"><system-log-history style="flex:1;min-height:300px"/></if></div></if><if :="subView === 'events'"><div style="display:flex;flex-direction:column;gap:12px;height:100%;min-height:0"><div style="font-weight:600;font-size:15px;opacity:.8;flex-shrink:0">Events</div><system-log :initial-source="'event'" style="flex:1;min-height:300px"/></div></if><if :="subView === 'sync'"><system-sync-panel style="height:100%;min-height:0"/></if><if :="subView === 'auth'"><system-auth-panel style="height:100%;min-height:0"/></if><if :="subView === 'api'"><system-api-panel style="height:100%;min-height:0"/></if><if :="subView === 'docs'"><div class="docs-wrap"><ok-doc-viewer :blocks="[]" root="/docs/" prefix="#/system/docs/" :file="docsCurrentFile" :anchor="docsAnchor" :manifest="docsManifest" sidebar-title="On this page" @anchor-change="docsOnAnchorChange($event.detail)"/></div></if><if :="subView === 'overview'"><div style="display:flex;flex-direction:column;gap:20px;max-width:900px"><div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap"><div style="font-weight:600;font-size:16px">Overview</div><button @click="load()" :disabled="loading" style="opacity:.8;font-size:12px">↻ Refresh</button> <label style="display:flex;align-items:center;gap:6px;font-size:12px;opacity:.7;cursor:pointer"><input type="checkbox" :checked="autoRefresh" @change="autoRefresh = $el.checked"/> Auto-refresh 10s</label><if :="loading"><span style="font-size:12px;opacity:.55">Loading…</span></if></div><if :="error"><div style="color:var(--okdb-danger);font-size:13px;padding:10px;border:1px solid var(--okdb-danger);border-radius:8px">{{error}}</div></if><div style="display:grid;grid-template-columns:1fr 1fr;gap:16px"><div class="card"><div class="card-title">Node Identity</div><div class="kv-grid"><div class="kv-key">Node ID</div><div class="kv-val" style="display:flex;align-items:center;gap:6px"><span style="font-family:monospace;font-size:12px;word-break:break-all">{{nodeId}}</span> <button @click="copyId()" style="font-size:10px;padding:2px 6px;opacity:.7">copy</button></div><if :="nodeAddress"><div class="kv-key">Address</div><div class="kv-val" style="font-family:monospace;font-size:12px">{{nodeAddress}}</div></if><div class="kv-key">Clock</div><div class="kv-val" style="font-family:monospace">{{formatNum(clock)}}</div><div class="kv-key">DB State</div><div class="kv-val" style="color:var(--okdb-success)">open</div><div class="kv-key">OKDB</div><div class="kv-val" style="font-family:monospace">v{{info?.versions?.okdb ?? info?.version ?? '--'}}</div><div class="kv-key">lmdb-js</div><div class="kv-val" style="font-family:monospace">v{{info?.versions?.lmdb ?? '--'}}</div><div class="kv-key">LMDB</div><div class="kv-val" style="font-family:monospace;font-size:11px;opacity:.7">{{info?.versions?.lmdbNative ?? '--'}}</div></div></div><div class="card"><div class="card-title">Storage (all environments)</div><div class="kv-grid"><div class="kv-key">Total Size</div><div class="kv-val" style="font-size:20px;font-weight:600;color:var(--okdb-accent)">{{approxSize}}</div><div class="kv-key">Environments</div><div class="kv-val">{{(envs || []).length}}</div><div class="kv-key">User Types</div><div class="kv-val">{{totalTypeCount}}</div><div class="kv-key">Page Size</div><div class="kv-val">{{formatNum(info?.pageSize)}} B</div><div class="kv-key">Map Size</div><div class="kv-val">{{info?.config?.lmdbOptions?.mapSize ? (info.config.lmdbOptions.mapSize/1024/1024/1024).toFixed(2)+' GB' : '--'}} <span style="opacity:.4;font-size:11px">(per env)</span></div></div></div></div><if :="envs && envs.length > 0"><div class="card"><div style="display:flex;align-items:center;gap:10px;margin-bottom:12px;flex-wrap:wrap"><div style="font-weight:600;font-size:14px;flex:1">Environments ({{userEnvs.length}}<if :="systemEnvs.length > 0"><span style="opacity:.4;font-size:12px;font-weight:400">+ {{systemEnvs.length}} system</span></if>)</div><span style="font-size:12px;opacity:.5">{{formatBytes(totalEnvSize)}}</span><if :="systemEnvs.length > 0"><button @click="showSystemEnvs = !showSystemEnvs" style="font-size:11px;padding:3px 8px;opacity:.6">{{showSystemEnvs ? '▴ hide system' : '⚙ show system'}}</button></if><button @click="openCreateEnv()" style="font-size:11px;padding:4px 10px;opacity:.8">+ Create</button> <button @click="openDemoModal()" style="font-size:11px;padding:4px 12px;background:linear-gradient(135deg,color-mix(in srgb,var(--okdb-accent) 7%,transparent),color-mix(in srgb,var(--okdb-purple) 7%,transparent));border:1px solid color-mix(in srgb,var(--okdb-accent) 27%,transparent);color:var(--okdb-accent);border-radius:6px;font-weight:600" title="Generate a demo environment">✦ Demo</button></div><table class="sys-table"><thead><tr><th>Name</th><th style="text-align:right">Size</th><th style="text-align:right">Clock</th><th style="text-align:right">Types</th><th style="text-align:center">Sync</th><th></th></tr></thead><tbody><tr as="repeat" :of="visibleEnvs" let:item="e" class="sys-table-row"><td style="font-family:monospace;font-size:12px"><a @click.prevent="goToEnv(e.name)" href="#" style="color:var(--okdb-accent);text-decoration:none" :title="'Explore '+e.name">{{e.name}}</a><if :="e.parentEnv"><span style="font-size:10px;opacity:.4;font-family:monospace;margin-left:4px">↳ {{e.parentEnv}}</span></if></td><td style="text-align:right;font-size:12px;font-family:monospace;opacity:.8">{{formatBytes(e.size)}}</td><td style="text-align:right;font-family:monospace;font-size:12px;opacity:.7">{{e.sync ? formatNum(e.clock) : '—'}}</td><td style="text-align:right;font-size:12px;opacity:.7">{{e.types ? e.types.length : 0}}</td><td style="text-align:center"><span :style="e.sync?'color:var(--okdb-success);font-size:11px;':'opacity:.4;font-size:11px;'">{{e.sync?'✓':'✗'}}</span></td><td style="text-align:right;white-space:nowrap"><button @click="compactEnv(e.name)" :disabled="_compactingEnvs[e.name]" :style="_compactingEnvs[e.name]?'font-size:10px;padding:2px 8px;opacity:.35;cursor:not-allowed;':'font-size:10px;padding:2px 8px;opacity:.55;'">{{_compactingEnvs[e.name]?'⏳ compacting…':'⬡ compact'}}</button><if :="e.name !== '~system'"><button @click="openRemoveEnv(e.name)" style="font-size:10px;padding:2px 8px;opacity:.45;color:var(--okdb-danger);border-color:color-mix(in srgb,var(--okdb-danger) 20%,transparent);margin-left:4px">🗑 remove</button></if></td></tr></tbody></table><if :="orphanedEnvs.length > 0"><div style="margin-top:16px;border-top:1px solid var(--okdb-app-border);padding-top:14px"><div style="font-size:12px;font-weight:600;opacity:.6;margin-bottom:8px;display:flex;align-items:center;gap:6px"><span style="color:var(--okdb-orange)">⚠</span> Orphaned sub-environments ({{orphanedEnvs.length}}) <span style="font-size:11px;font-weight:400;opacity:.55;margin-left:2px">— parent was removed; data on disk is intact</span></div><table class="sys-table"><tbody><tr as="repeat" :of="orphanedEnvs" let:item="e" class="sys-table-row"><td style="font-family:monospace;font-size:12px;opacity:.5;text-decoration:line-through">{{e.name}}</td><td style="font-size:11px;opacity:.4;font-style:italic">orphaned from <span style="font-family:monospace">{{e.orphanedFrom ?? '—'}}</span></td><td style="text-align:right;white-space:nowrap"><button @click="dismissOrphan(e.name)" style="font-size:10px;padding:2px 8px;opacity:.5;color:var(--okdb-danger);border-color:color-mix(in srgb,var(--okdb-danger) 20%,transparent)">✕ dismiss</button></td></tr></tbody></table></div></if></div></if><div class="card" style="border:1px solid color-mix(in srgb,var(--okdb-accent) 9%,transparent);background:linear-gradient(135deg,var(--okdb-app-bg-deep),var(--okdb-app-surface))"><div style="display:flex;align-items:center;gap:14px;flex-wrap:wrap"><div style="flex:1;min-width:200px"><div style="font-weight:600;font-size:14px;margin-bottom:4px;display:flex;align-items:center;gap:8px"><span style="color:var(--okdb-accent);opacity:.7">⬦</span> Data Operations</div><div style="font-size:12px;opacity:.45;line-height:1.5">Export & import logical NDJSON snapshots · hot filesystem backup</div></div><div style="display:flex;gap:8px;flex-shrink:0;flex-wrap:wrap;align-items:center"><button @click="openDataOps('export')" style="display:flex;align-items:center;gap:6px;padding:7px 15px;border-radius:8px;font-size:12px;font-weight:600;background:linear-gradient(135deg,var(--okdb-app-bg-deep),var(--okdb-app-bg-deep));border:1px solid color-mix(in srgb,var(--okdb-accent) 27%,transparent);color:var(--okdb-accent);cursor:pointer;white-space:nowrap">⬇ Export</button> <button @click="openDataOps('import')" style="display:flex;align-items:center;gap:6px;padding:7px 15px;border-radius:8px;font-size:12px;font-weight:600;background:linear-gradient(135deg,var(--okdb-app-bg-deep),var(--okdb-app-bg-deep));border:1px solid color-mix(in srgb,var(--okdb-accent) 27%,transparent);color:var(--okdb-accent);cursor:pointer;white-space:nowrap">⬆ Import</button><div style="width:1px;height:24px;background:var(--okdb-app-border);margin:0 2px;flex-shrink:0"></div><button @click="openDataOps('backup')" style="display:flex;align-items:center;gap:6px;padding:7px 15px;border-radius:8px;font-size:12px;font-weight:600;background:linear-gradient(135deg,var(--okdb-app-bg-deep),var(--okdb-app-bg-deep));border:1px solid color-mix(in srgb,var(--okdb-purple) 27%,transparent);color:var(--okdb-purple);cursor:pointer;white-space:nowrap">🗄 Backup</button></div></div></div><div style="display:grid;grid-template-columns:1fr 1fr;gap:16px"><div class="card"><div class="card-title">Configuration</div><div class="kv-grid"><div class="kv-key">Durability</div><div class="kv-val" style="display:flex;align-items:center;gap:6px"><span :style="'font-size:11px;font-weight:600;padding:2px 8px;border-radius:999px;border:1px solid;color:'+durabilityLabel.color+';border-color:'+durabilityLabel.color+'44;background:'+durabilityLabel.color+'11;'">{{durabilityLabel.label}}</span> <span style="opacity:.5;font-size:11px;flex:1">{{durabilityLabel.hint}}</span></div><div class="kv-key">Compression</div><div class="kv-val"><span :style="config?.lmdbOptions?.compression?'color:var(--okdb-success)':'opacity:.5'">{{config?.lmdbOptions?.compression?'LZ4 enabled':'disabled'}}</span></div><div class="kv-key">Encryption</div><div class="kv-val"><span :style="config?.lmdbOptions?.encryptionKey?'color:var(--okdb-success)':'opacity:.5'">{{config?.lmdbOptions?.encryptionKey?'Chacha8 enabled':'none'}}</span></div><div class="kv-key">Map Size</div><div class="kv-val">{{formatBytes(config?.lmdbOptions?.mapSize)}}</div><div class="kv-key">Max DBs</div><div class="kv-val">{{formatNum(config?.lmdbOptions?.maxDbs)}}</div></div><if :="configWarnings.length > 0"><div style="margin-top:10px;display:flex;flex-direction:column;gap:4px"><for :="configWarnings" let:w><div style="font-size:11px;color:var(--okdb-warning);background:color-mix(in srgb,var(--okdb-warning) 7%,transparent);border:1px solid color-mix(in srgb,var(--okdb-warning) 20%,transparent);border-radius:6px;padding:5px 8px;line-height:1.4">⚠ {{w}}</div></for></div></if></div><div class="card"><div class="card-title">Process</div><if :="!runtime"><div style="opacity:.4;font-size:12px">Loading…</div></if><if :="runtime"><div class="kv-grid"><div class="kv-key">PID</div><div class="kv-val" style="font-family:monospace">{{runtime.process?.pid}}</div><div class="kv-key">Uptime</div><div class="kv-val">{{formatUptime(runtime.process?.uptime)}}</div><div class="kv-key">Node.js</div><div class="kv-val" style="font-family:monospace">v{{runtime.process?.node}}</div><div class="kv-key" style="align-self:center">CPU</div><div class="kv-val" style="display:flex;align-items:center;gap:6px"><div style="flex:1;height:6px;background:var(--okdb-app-border);border-radius:3px;overflow:hidden"><div :style="'height:100%;border-radius:3px;transition:width .4s;background:'+(runtime.process?.cpu?.pct>80?'var(--okdb-danger)':runtime.process?.cpu?.pct>40?'var(--okdb-warning)':'var(--okdb-success)')+';width:'+(runtime.process?.cpu?.pct??0)+'%;'"></div></div><span style="font-size:11px;opacity:.6;min-width:30px">{{runtime.process?.cpu?.pct??0}}%</span></div><div class="kv-key">RSS</div><div class="kv-val">{{formatBytes(runtime.process?.memory?.rss)}}</div><div class="kv-key">Heap Used</div><div class="kv-val">{{formatBytes(runtime.process?.memory?.heapUsed)}} <span style="opacity:.45;font-size:11px">/ {{formatBytes(runtime.process?.memory?.heapTotal)}}</span></div><div class="kv-key" style="align-self:center">Heap %</div><div class="kv-val" style="display:flex;align-items:center;gap:6px"><div style="flex:1;height:6px;background:var(--okdb-app-border);border-radius:3px;overflow:hidden"><div :style="'height:100%;background:var(--okdb-accent);border-radius:3px;width:'+pct(runtime.process?.memory?.heapUsed,runtime.process?.memory?.heapTotal)+'%;'"></div></div><span style="font-size:11px;opacity:.6;min-width:30px">{{pct(runtime.process?.memory?.heapUsed,runtime.process?.memory?.heapTotal)}}%</span></div><div class="kv-key">External</div><div class="kv-val">{{formatBytes(runtime.process?.memory?.external)}}</div></div></if></div></div><div class="card"><div class="card-title">Machine</div><if :="!runtime"><div style="opacity:.4;font-size:12px">Loading…</div></if><if :="runtime"><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px"><div><div style="font-size:11px;opacity:.5;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">Memory</div><div style="font-size:22px;font-weight:600;color:var(--okdb-accent)">{{formatBytes(runtime.os?.freeMemory)}}</div><div style="font-size:11px;opacity:.5;margin-top:2px">free of {{formatBytes(runtime.os?.totalMemory)}}</div><div style="margin-top:8px;height:6px;background:var(--okdb-app-border);border-radius:3px;overflow:hidden"><div :style="'height:100%;background:var(--okdb-accent);border-radius:3px;width:'+pct(runtime.os?.totalMemory-runtime.os?.freeMemory,runtime.os?.totalMemory)+'%;'"></div></div><div style="font-size:11px;opacity:.45;margin-top:3px">{{pct(runtime.os?.totalMemory-runtime.os?.freeMemory,runtime.os?.totalMemory)}}% used</div></div><div><div style="font-size:11px;opacity:.5;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">CPU</div><div style="font-size:14px;font-weight:600">{{runtime.os?.cpuCount}} core{{runtime.os?.cpuCount!==1?'s':''}}</div><div style="font-size:11px;opacity:.5;margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis" :title="runtime.os?.cpuModel">{{runtime.os?.cpuModel}}</div><div style="margin-top:8px;font-size:11px;opacity:.6">Load avg (1m/5m/15m)</div><div style="font-size:13px;font-family:monospace;margin-top:2px">{{formatLoad(runtime.os?.loadAvg)}}</div></div><div><div style="font-size:11px;opacity:.5;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">Platform</div><div style="font-size:14px;font-weight:600;text-transform:capitalize">{{runtime.os?.platform}}</div><div style="font-size:11px;opacity:.5;margin-top:3px">{{runtime.os?.arch}}</div><div style="font-size:11px;opacity:.35;margin-top:6px;word-break:break-all">{{runtime.os?.release}}</div><div style="font-size:11px;opacity:.4;margin-top:3px;font-family:monospace">{{runtime.os?.hostname}}</div></div></div></if></div><if :="metrics"><div class="card"><div class="card-title">API Metrics</div><div style="display:grid;grid-template-columns:1fr 1fr;gap:16px"><div><div style="font-size:11px;opacity:.5;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">Lifetime</div><div class="kv-grid"><div class="kv-key">Requests</div><div class="kv-val" style="color:var(--okdb-accent);font-weight:600">{{formatNum(metrics.lifetime?.requests)}}</div><div class="kv-key">Reads</div><div class="kv-val">{{formatNum(metrics.lifetime?.reads)}}</div><div class="kv-key">Writes</div><div class="kv-val">{{formatNum(metrics.lifetime?.writes)}}</div><div class="kv-key">Errors</div><div class="kv-val" :style="metrics.lifetime?.errors>0?'color:var(--okdb-danger);':'opacity:.5;'">{{formatNum(metrics.lifetime?.errors)}}</div><div class="kv-key">Avg Read</div><div class="kv-val" style="font-family:monospace">{{metrics.lifetime?.avgReadMs!=null?metrics.lifetime.avgReadMs+'ms':'—'}}</div><div class="kv-key">Avg Write</div><div class="kv-val" style="font-family:monospace">{{metrics.lifetime?.avgWriteMs!=null?metrics.lifetime.avgWriteMs+'ms':'—'}}</div></div></div><div><div style="font-size:11px;opacity:.5;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">Last 60 seconds</div><div class="kv-grid"><div class="kv-key">Req/s</div><div class="kv-val" style="color:var(--okdb-success);font-weight:600">{{metrics.window?.reqPerSec}}</div><div class="kv-key">Read/s</div><div class="kv-val">{{metrics.window?.readPerSec}}</div><div class="kv-key">Write/s</div><div class="kv-val">{{metrics.window?.writePerSec}}</div><div class="kv-key">Requests</div><div class="kv-val">{{formatNum(metrics.window?.requests)}}</div><div class="kv-key">Avg Read</div><div class="kv-val" style="font-family:monospace">{{metrics.window?.avgReadMs!=null?metrics.window.avgReadMs+'ms':'—'}}</div><div class="kv-key">Avg Write</div><div class="kv-val" style="font-family:monospace">{{metrics.window?.avgWriteMs!=null?metrics.window.avgWriteMs+'ms':'—'}}</div></div></div></div></div></if><if :="plugins.length > 0"><div class="card"><div class="card-title">Plugins ({{plugins.length}})</div><div style="display:flex;flex-wrap:wrap;gap:8px"><for :="plugins" let:p><span style="padding:4px 12px;border-radius:999px;font-size:12px;border:1px solid color-mix(in srgb,var(--okdb-accent) 33%,transparent);color:var(--okdb-accent);background:color-mix(in srgb,var(--okdb-accent) 7%,transparent)">{{p}}</span></for></div></div></if><if :="typeList.length > 0"><div class="card"><div class="card-title">Types ({{typeList.length}} across all environments)</div><table class="sys-table"><thead><tr><th>Type</th><th>Environment</th><th style="text-align:right">Records</th></tr></thead><tbody><tr as="repeat" :of="typeList" let:item="entry"><td style="font-family:monospace;font-size:12px">{{entry.name}}</td><td style="font-family:monospace;font-size:11px;opacity:.55">{{entry.env}}</td><td style="text-align:right;color:var(--okdb-accent)">{{formatNum(entry.count)}}</td></tr></tbody></table></div></if><if :="systemTypeList.length > 0"><div class="card"><details><summary style="cursor:pointer;font-weight:600;font-size:13px;list-style:none;display:flex;align-items:center;gap:8px"><span>▶</span><span>Internal / System Types ({{systemTypeList.length}})</span></summary><table class="sys-table" style="margin-top:10px"><thead><tr><th>Type</th><th>Environment</th><th style="text-align:right">Records</th></tr></thead><tbody><tr as="repeat" :of="systemTypeList" let:item="entry"><td style="font-family:monospace;font-size:12px;opacity:.7">{{entry.name}}</td><td style="font-family:monospace;font-size:11px;opacity:.45">{{entry.env}}</td><td style="text-align:right;color:var(--okdb-accent);opacity:.7">{{formatNum(entry.count)}}</td></tr></tbody></table></details></div></if><div class="card"><div style="display:flex;align-items:center;gap:10px;cursor:pointer" @click="toggleChangelog()"><div style="font-weight:600;font-size:14px;flex:1">System Change Log</div><span style="font-size:12px;opacity:.5">all environments</span> <span style="opacity:.5;font-size:12px">{{changelogOpen ? '▲' : '▼'}}</span></div><if :="changelogOpen"><div style="margin-top:12px;display:flex;flex-direction:column;gap:8px"><if :="changelogError"><div style="color:var(--okdb-danger);font-size:12px">{{changelogError}}</div></if><div style="display:flex;gap:6px;align-items:center"><button @click="loadChangelog()" :disabled="changelogBusy" style="font-size:12px;opacity:.8">↻ Refresh</button> <span style="font-size:12px;opacity:.5;margin-left:auto">showing {{changes.length}} entries</span></div><div style="overflow:auto;max-height:400px"><table style="width:100%;border-collapse:collapse;font-size:12px"><thead style="position:sticky;top:0;background:var(--okdb-app-surface);z-index:1"><tr><th class="cl-th" style="width:80px">Env</th><th class="cl-th" style="width:110px">Action</th><th class="cl-th" style="width:80px">Type</th><th class="cl-th">Key</th><th class="cl-th" style="width:90px;text-align:right">When</th></tr></thead><tbody><tr as="repeat" :of="changes" let:item="ch" class="cl-row"><td class="cl-td" style="font-family:monospace;font-size:10px;opacity:.5;white-space:nowrap;max-width:80px;overflow:hidden;text-overflow:ellipsis" :title="ch._env">{{ch._env||'default'}}</td><td class="cl-td"><span :style="'font-size:10px;padding:2px 5px;border-radius:3px;border:1px solid;color:'+actionColor(ch.action)+';border-color:'+actionColor(ch.action)+'44;'">{{actionLabel(ch.action)}}</span></td><td class="cl-td" style="font-family:monospace;font-size:11px;opacity:.75;max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" :title="ch.type">{{ch.type||'—'}}</td><td class="cl-td" style="font-family:monospace;font-size:11px;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" :title="ch.key">{{shortKey(ch.key)}}</td><td class="cl-td" style="text-align:right;opacity:.55;white-space:nowrap;font-size:11px">{{relTime(ch.timestamp)}}</td></tr></tbody></table></div><if :="changelogHasOlder"><button @click="loadMoreChangelog()" :disabled="changelogBusy" style="font-size:12px;align-self:flex-end">Load more ▼ ({{changelogLimit+100}})</button></if></div></if></div></div></if><if :="subView === 'license'"><license-panel/></if></template></component></template><style>.sys-subnav{display:flex;flex-direction:column;gap:2px}.sys-subnav__item{display:flex;align-items:center;gap:6px;width:100%;text-align:left;padding:6px 10px;border-radius:7px;background:0 0;border-color:transparent;color:var(--okdb-app-fg-muted);font-size:12px;cursor:pointer;transition:color .13s,background .13s,border-color .13s;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sys-subnav__item:hover{background:var(--okdb-app-surface-2);border-color:var(--okdb-app-border);color:var(--okdb-app-fg)}.sys-subnav__item--active{background:var(--okdb-app-bg-deep)!important;border-color:color-mix(in srgb,var(--okdb-accent) 27%,transparent)!important;color:var(--okdb-accent)!important}.sys-subnav__icon{font-size:12px;opacity:.7;flex-shrink:0}.sys-subnav__item--active .sys-subnav__icon{opacity:1}.sys-subnav__divider{height:1px;background:var(--okdb-app-surface);margin:3px 2px}.docs-nav{display:flex;flex-direction:column;gap:1px;margin:0 -10px;padding:2px 0}.docs-nav__item{display:block;width:100%;text-align:left;padding:5px 14px;border-radius:0;border:none;border-left:2px solid transparent;background:0 0;color:var(--okdb-app-fg-muted);font-size:12.5px;cursor:pointer;transition:color .13s,background .13s,border-color .13s;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.55;letter-spacing:.01em}.docs-nav__item--standalone{font-weight:500;color:var(--okdb-app-fg);margin-top:2px}.docs-nav__item:hover{background:var(--okdb-app-surface-2);color:var(--okdb-app-fg);border-left-color:var(--okdb-app-border-strong)}.docs-nav__item--active{background:var(--okdb-accent-soft-bg)!important;color:var(--okdb-accent)!important;border-left-color:var(--okdb-accent)!important;font-weight:500}.docs-nav__group{margin-top:6px;padding-top:4px;border-top:1px solid var(--okdb-app-surface)}.docs-nav__group-label{display:block;font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--okdb-app-fg-faint);padding:4px 14px 5px;pointer-events:none;user-select:none}.docs-wrap{margin:-20px -24px -6px;min-height:calc(100% + 26px)}.docs-wrap ok-doc-viewer{--ok_color-accent:var(--okdb-accent);--ok_color-fg:var(--okdb-app-fg);--ok_color-muted-fg:var(--okdb-app-fg-muted);--ok_color-muted-border:var(--okdb-app-border);--font-mono:'Cascadia Code','Fira Code','Consolas',monospace;display:block;min-height:100%}.docs-wrap ok-doc-viewer .md table{border-collapse:collapse;width:100%;margin:1.2rem 0;font-size:13px}.docs-wrap ok-doc-viewer .md td,.docs-wrap ok-doc-viewer .md th{border:1px solid var(--okdb-app-border);padding:7px 12px;text-align:left}.docs-wrap ok-doc-viewer .md th{background:var(--okdb-app-surface-2);color:#8fc8e8;font-weight:600;font-size:12px;letter-spacing:.03em}.docs-wrap ok-doc-viewer .md tr:hover td{background:var(--okdb-app-surface)}.docs-wrap ok-doc-viewer .md a{color:var(--okdb-accent);text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--okdb-accent) 27%,transparent);transition:border-color .15s,color .15s}.docs-wrap ok-doc-viewer .md a:hover{color:#7fd8ff;border-bottom-color:#7fd8ff88}.docs-wrap ok-doc-viewer .md code:not(pre code){background:var(--okdb-app-bg-deep);border:1px solid var(--okdb-app-border);color:var(--okdb-accent);padding:1px 6px;border-radius:4px;font-size:.9em}.docs-wrap ok-doc-viewer .md blockquote{border-left:3px solid color-mix(in srgb,var(--okdb-accent) 27%,transparent);margin:1rem 0;padding:6px 14px;background:var(--okdb-app-surface);border-radius:0 6px 6px 0;color:var(--okdb-app-fg-muted);font-style:italic}.card{background:var(--okdb-app-surface);border-radius:12px;padding:16px;box-shadow:0 0 5px #ffffff1a}.card-title{font-weight:600;margin-bottom:12px;font-size:14px}.kv-grid{display:grid;grid-template-columns:110px 1fr;gap:6px 12px;font-size:13px}.kv-key{opacity:.6}.kv-val{word-break:break-all}.sys-table{width:100%;border-collapse:collapse;font-size:13px}.sys-table th{text-align:left;padding:6px 8px;border-bottom:1px solid var(--okdb-app-border);opacity:.6;font-weight:500;font-size:11px;text-transform:uppercase;letter-spacing:.04em}.sys-table td{padding:5px 8px;border-bottom:1px solid var(--okdb-app-border)}.sys-table tr:last-child td{border-bottom:none}.sys-table tr:hover td{background:#ffffff08}.cl-th{padding:5px 8px;text-align:left;font-weight:500;font-size:10px;text-transform:uppercase;letter-spacing:.04em;opacity:.5;border-bottom:1px solid var(--okdb-app-border)}.cl-td{padding:5px 8px;border-bottom:1px solid var(--okdb-app-border)}.cl-row:hover .cl-td{background:#ffffff06}</style>
|