@reddb-io/ui 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/_app/immutable/assets/0.BWZTMKZW.css +1 -0
- package/build/_app/immutable/assets/AnalyticsView.BLAJezw4.css +1 -0
- package/build/_app/immutable/assets/{Workspace.CvP5De7i.css → Workspace.DM2E1WiF.css} +1 -1
- package/build/_app/immutable/chunks/{C9rXzYLu.js → B-yl15ta.js} +1 -1
- package/build/_app/immutable/chunks/B5euRbKa.js +1 -0
- package/build/_app/immutable/chunks/BRIMmWzq.js +1 -0
- package/build/_app/immutable/chunks/BgOTHNR5.js +1 -0
- package/build/_app/immutable/chunks/Bgb6fHGb.js +18 -0
- package/build/_app/immutable/chunks/Btd6PFkC.js +1 -0
- package/build/_app/immutable/chunks/C-kqMbRi.js +1 -0
- package/build/_app/immutable/chunks/C3SGj6Qd.js +2 -0
- package/build/_app/immutable/chunks/{C278awGv.js → C7kf23SQ.js} +1 -1
- package/build/_app/immutable/chunks/CGFL56WJ.js +1 -0
- package/build/_app/immutable/chunks/CHvQYbpl.js +1 -0
- package/build/_app/immutable/chunks/CO7K4bD2.js +1164 -0
- package/build/_app/immutable/chunks/CP65IkJc.js +1 -0
- package/build/_app/immutable/chunks/CxnvJShw.js +404 -0
- package/build/_app/immutable/chunks/D02PB2u7.js +1 -0
- package/build/_app/immutable/chunks/DCJ0QyYH.js +1 -0
- package/build/_app/immutable/chunks/DN7v3Tar.js +2 -0
- package/build/_app/immutable/chunks/DRlh90Cv.js +1 -0
- package/build/_app/immutable/entry/app.BeX4Tmu5.js +2 -0
- package/build/_app/immutable/entry/start.D7y1MZex.js +1 -0
- package/build/_app/immutable/nodes/0.C0e5KQVb.js +2 -0
- package/build/_app/immutable/nodes/1.ceiVuZbB.js +1 -0
- package/build/_app/immutable/nodes/10.DeFqKRR2.js +96 -0
- package/build/_app/immutable/nodes/12.CzMvqoYS.js +3 -0
- package/build/_app/immutable/nodes/14.CAG5_cGP.js +1 -0
- package/build/_app/immutable/nodes/15.CRDBqbiH.js +60 -0
- package/build/_app/immutable/nodes/8.CAG5_cGP.js +1 -0
- package/build/_app/version.json +1 -1
- package/build/index.html +18 -15
- package/dist/embed/AnalyticsView-SKyGxeAx.js +23337 -0
- package/dist/embed/AnalyticsView-SKyGxeAx.js.map +1 -0
- package/dist/embed/RedUiEmbedRoot-BzVzGAFK.js +31960 -0
- package/dist/embed/RedUiEmbedRoot-BzVzGAFK.js.map +1 -0
- package/dist/embed/event-BvRbkLjD.js +46 -0
- package/dist/embed/event-BvRbkLjD.js.map +1 -0
- package/dist/embed/index-0hA141D2.js +85 -0
- package/dist/embed/index-0hA141D2.js.map +1 -0
- package/dist/embed/index-BVxMkfHe.js +8 -0
- package/dist/embed/index-BVxMkfHe.js.map +1 -0
- package/dist/embed/index-BY6fc1oQ.js +856 -0
- package/dist/embed/index-BY6fc1oQ.js.map +1 -0
- package/dist/embed/index-client-BHUUkI8H.js +3304 -0
- package/dist/embed/index-client-BHUUkI8H.js.map +1 -0
- package/dist/embed/index.js +1 -1
- package/dist/embed/{sigma.esm-D5yoExsH.js → sigma.esm-BawyGZ3s.js} +2 -2
- package/dist/embed/{sigma.esm-D5yoExsH.js.map → sigma.esm-BawyGZ3s.js.map} +1 -1
- package/dist/embed/window-ZR8_mM8o.js +1889 -0
- package/dist/embed/window-ZR8_mM8o.js.map +1 -0
- package/package.json +7 -3
- package/src/app.css +86 -20
- package/src/lib/AnalyticsView.svelte +209 -0
- package/src/lib/AskView.svelte +255 -0
- package/src/lib/ClusterView.svelte +415 -95
- package/src/lib/CommandPalette.svelte +2 -0
- package/src/lib/ConnectDropdown.svelte +60 -3
- package/src/lib/DevConsole.svelte +155 -0
- package/src/lib/ErrorBoundary.fixture.svelte +11 -0
- package/src/lib/ErrorBoundary.fixtures.svelte.ts +5 -0
- package/src/lib/ErrorBoundary.svelte +78 -0
- package/src/lib/ErrorBoundaryCrashChild.fixture.svelte +9 -0
- package/src/lib/GlobalProgressBar.svelte +9 -0
- package/src/lib/LoadingOverlay.svelte +109 -0
- package/src/lib/MetricChart.svelte +85 -0
- package/src/lib/QueryEditor.svelte +48 -7
- package/src/lib/ResultsPane.svelte +76 -8
- package/src/lib/SchemaTree.svelte +25 -1
- package/src/lib/SecurityView.svelte +176 -65
- package/src/lib/SettingsView.svelte +1111 -254
- package/src/lib/StatusBar.svelte +19 -1
- package/src/lib/Topbar.svelte +29 -1
- package/src/lib/Workspace.svelte +136 -5
- package/src/lib/activity.svelte.ts +44 -35
- package/src/lib/collection-preview.ts +14 -0
- package/src/lib/connections.svelte.ts +167 -4
- package/src/lib/embed/RedUiEmbedRoot.svelte +13 -8
- package/src/lib/embed/index.ts +34 -1
- package/src/lib/index.ts +10 -0
- package/src/lib/loading.svelte.ts +142 -0
- package/src/lib/permission-gate.ts +120 -0
- package/src/lib/policies.ts +136 -0
- package/src/lib/query-tabs.svelte.ts +132 -53
- package/src/lib/reddb/analytics.ts +208 -0
- package/src/lib/reddb/ask.ts +123 -0
- package/src/lib/reddb/client.ts +292 -73
- package/src/lib/reddb/cluster-status.ts +306 -0
- package/src/lib/reddb/desktop-error.ts +89 -0
- package/src/lib/reddb/dev-console.ts +186 -0
- package/src/lib/reddb/index.ts +5 -0
- package/src/lib/reddb/local-url-provider.ts +28 -1
- package/src/lib/reddb/redwire-ws-spike.ts +590 -0
- package/src/lib/reddb/tauri-encrypted-store.ts +4 -9
- package/src/lib/router.svelte.ts +12 -0
- package/src/lib/settings-authoring-client.ts +336 -0
- package/src/lib/settings-sections.ts +292 -196
- package/src/lib/tabs.svelte.ts +5 -0
- package/src/lib/workspace-session.ts +81 -0
- package/build/_app/immutable/assets/0.DQDLkSdR.css +0 -1
- package/build/_app/immutable/chunks/65XjEwaF.js +0 -367
- package/build/_app/immutable/chunks/BGcIe2ep.js +0 -2
- package/build/_app/immutable/chunks/BjFADNkA.js +0 -992
- package/build/_app/immutable/chunks/BrvvOVaJ.js +0 -1
- package/build/_app/immutable/chunks/D9CTkMjK.js +0 -1
- package/build/_app/immutable/chunks/oiO4RLqp.js +0 -1
- package/build/_app/immutable/chunks/pDJnDxXM.js +0 -1
- package/build/_app/immutable/entry/app.DdaMXHAv.js +0 -2
- package/build/_app/immutable/entry/start.-MLBtQXB.js +0 -1
- package/build/_app/immutable/nodes/0.Ysoz3Y3h.js +0 -1
- package/build/_app/immutable/nodes/1.C2jbGTOJ.js +0 -1
- package/build/_app/immutable/nodes/12.DQG5zpEx.js +0 -60
- package/build/_app/immutable/nodes/8.CrHI4F9p.js +0 -134
- package/dist/embed/RedUiEmbedRoot-BwFUA5X-.js +0 -28624
- package/dist/embed/RedUiEmbedRoot-BwFUA5X-.js.map +0 -1
- package/dist/embed/index-DGhgtnaK.js +0 -577
- package/dist/embed/index-DGhgtnaK.js.map +0 -1
- package/dist/embed/index-client-ItCEU64C.js +0 -3087
- package/dist/embed/index-client-ItCEU64C.js.map +0 -1
- /package/build/_app/immutable/assets/{12.Dblyk0Kr.css → 15.Dblyk0Kr.css} +0 -0
- /package/build/_app/immutable/nodes/{10.CAG5_cGP.js → 13.CAG5_cGP.js} +0 -0
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { tabs, type Tab } from '$lib/tabs.svelte'
|
|
8
8
|
import { queryTabs } from '$lib/query-tabs.svelte'
|
|
9
9
|
import { defaultSubpage, subpageCapability } from '$lib/collection-pages'
|
|
10
|
+
import { collectionPreviewQuery, safeCollectionName } from '$lib/collection-preview'
|
|
10
11
|
import {
|
|
11
12
|
collectionCatalogBadges,
|
|
12
13
|
collectionCatalogFromRow,
|
|
@@ -38,6 +39,8 @@
|
|
|
38
39
|
{ value: 'document', label: 'Document' },
|
|
39
40
|
{ value: 'json', label: 'JSON' },
|
|
40
41
|
]
|
|
42
|
+
const resultsSkeletonRows = Array.from({ length: 14 }, (_, i) => i)
|
|
43
|
+
const resultsSkeletonColumns = Array.from({ length: 6 }, (_, i) => i)
|
|
41
44
|
|
|
42
45
|
let results = $state<Record<string, QueryResult | null>>({})
|
|
43
46
|
let errors = $state<Record<string, string>>({})
|
|
@@ -169,10 +172,6 @@
|
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
function safeCollectionName(collection: string): string {
|
|
173
|
-
return collection.replace(/[^A-Za-z0-9_./-]/g, '')
|
|
174
|
-
}
|
|
175
|
-
|
|
176
175
|
function sqlString(s: string): string {
|
|
177
176
|
return `'${s.replace(/'/g, "''")}'`
|
|
178
177
|
}
|
|
@@ -240,7 +239,8 @@
|
|
|
240
239
|
const shouldFetchQueue = subpage === 'queue' || tab.capability === 'queue'
|
|
241
240
|
const shouldFetchVector = subpage === 'vector' || tab.capability === 'vector'
|
|
242
241
|
const shouldFetchStats = subpage === 'stats' || tab.capability === 'stats'
|
|
243
|
-
const
|
|
242
|
+
const shouldFetchKv = subpage === 'kv' || tab.capability === 'kv'
|
|
243
|
+
const fetchMode = shouldFetchGraph ? 'graph-subgraph' : shouldFetchQueue ? 'queue-peek' : shouldFetchVector ? 'vector-inspector' : shouldFetchStats ? 'stats-info' : shouldFetchKv ? 'kv-list' : tab.capability ?? 'unknown'
|
|
244
244
|
const cacheKey = `${tab.key}:${subpage}:${fetchMode}`
|
|
245
245
|
if (resultKeys[tab.id] && resultKeys[tab.id] !== cacheKey) {
|
|
246
246
|
delete results[tab.id]
|
|
@@ -267,9 +267,14 @@
|
|
|
267
267
|
`${collection} · stats preview`,
|
|
268
268
|
() => fetchStatsCollection(client, collection),
|
|
269
269
|
)
|
|
270
|
+
: shouldFetchKv
|
|
271
|
+
? activity.track(
|
|
272
|
+
`${collection} · LIST KV (LIMIT 200)`,
|
|
273
|
+
() => client.query(collectionPreviewQuery(collection, 'kv')),
|
|
274
|
+
)
|
|
270
275
|
: activity.track(
|
|
271
276
|
`${collection} · SELECT * (LIMIT 200)`,
|
|
272
|
-
() => client.query(
|
|
277
|
+
() => client.query(collectionPreviewQuery(collection, tab.capability)),
|
|
273
278
|
)
|
|
274
279
|
job
|
|
275
280
|
.then((r) => { results[tab.id] = r })
|
|
@@ -588,8 +593,71 @@
|
|
|
588
593
|
<pre class="whitespace-pre-wrap">{err}</pre>
|
|
589
594
|
</div>
|
|
590
595
|
{:else if !result}
|
|
591
|
-
<div class="h-full
|
|
592
|
-
|
|
596
|
+
<div class="h-full flex flex-col" aria-busy="true" aria-label={`Loading ${tab.label} results`}>
|
|
597
|
+
<div class="border-b border-line-1 bg-bg-1/35 px-3 py-2">
|
|
598
|
+
<div class="flex flex-wrap items-center gap-x-4 gap-y-2">
|
|
599
|
+
<div class="min-w-[180px] flex-1">
|
|
600
|
+
<div class="flex items-center gap-2">
|
|
601
|
+
<div class="h-[21px] w-[78px] rounded border border-line-1 bg-bg-2 motion-safe:animate-pulse"></div>
|
|
602
|
+
<div class="h-4 w-44 max-w-[45vw] rounded bg-bg-2 motion-safe:animate-pulse"></div>
|
|
603
|
+
</div>
|
|
604
|
+
<div class="mt-1 flex flex-wrap gap-1">
|
|
605
|
+
<div class="h-[21px] w-24 rounded border border-line-1 bg-bg-0/70 motion-safe:animate-pulse"></div>
|
|
606
|
+
<div class="h-[21px] w-32 rounded border border-line-1 bg-bg-0/70 motion-safe:animate-pulse"></div>
|
|
607
|
+
<div class="h-[21px] w-20 rounded border border-line-1 bg-bg-0/70 motion-safe:animate-pulse"></div>
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
|
|
611
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
612
|
+
<div class="h-[30px] w-[94px] rounded border border-line-1 bg-bg-0/70 motion-safe:animate-pulse"></div>
|
|
613
|
+
<div class="h-[30px] w-[92px] rounded border border-line-1 bg-bg-0/70 motion-safe:animate-pulse"></div>
|
|
614
|
+
<div class="h-[30px] w-[92px] rounded border border-line-1 bg-bg-0/70 motion-safe:animate-pulse"></div>
|
|
615
|
+
<div class="h-[30px] w-[78px] rounded border border-line-1 bg-bg-0/70 motion-safe:animate-pulse"></div>
|
|
616
|
+
<div class="h-[30px] w-[78px] rounded border border-line-1 bg-bg-0/70 motion-safe:animate-pulse"></div>
|
|
617
|
+
</div>
|
|
618
|
+
</div>
|
|
619
|
+
</div>
|
|
620
|
+
|
|
621
|
+
<div class="border-b border-line-1 px-3 py-1.5 flex items-center gap-2 bg-bg-1/40">
|
|
622
|
+
<div class="h-6 w-[70px] rounded border border-line-1 bg-bg-2 motion-safe:animate-pulse"></div>
|
|
623
|
+
<div class="h-6 w-[54px] rounded border border-line-1 bg-bg-2 motion-safe:animate-pulse"></div>
|
|
624
|
+
<div class="h-4 w-px bg-line-1"></div>
|
|
625
|
+
<div class="h-6 w-[76px] rounded border border-line-1 bg-bg-2 motion-safe:animate-pulse"></div>
|
|
626
|
+
<div class="ml-auto h-3 w-[58px] rounded bg-bg-2 motion-safe:animate-pulse"></div>
|
|
627
|
+
</div>
|
|
628
|
+
|
|
629
|
+
<div class="flex-1 overflow-hidden">
|
|
630
|
+
<div class="min-w-[760px]">
|
|
631
|
+
<div class="grid h-8 grid-cols-[40px_repeat(6,minmax(96px,1fr))] border-b border-line-1 bg-bg-1">
|
|
632
|
+
<div class="px-2 py-2">
|
|
633
|
+
<div class="ml-auto h-3 w-4 rounded bg-bg-2 motion-safe:animate-pulse"></div>
|
|
634
|
+
</div>
|
|
635
|
+
{#each resultsSkeletonColumns as col (col)}
|
|
636
|
+
<div class="px-2 py-2">
|
|
637
|
+
<div class="h-3 rounded bg-bg-2 motion-safe:animate-pulse" style={`width: ${col % 3 === 0 ? 72 : col % 3 === 1 ? 104 : 56}px`}></div>
|
|
638
|
+
</div>
|
|
639
|
+
{/each}
|
|
640
|
+
</div>
|
|
641
|
+
{#each resultsSkeletonRows as row (row)}
|
|
642
|
+
<div class="grid h-[29px] grid-cols-[40px_repeat(6,minmax(96px,1fr))] border-b border-line-1/60">
|
|
643
|
+
<div class="px-2 py-2">
|
|
644
|
+
<div class="ml-auto h-3 w-4 rounded bg-bg-2 motion-safe:animate-pulse"></div>
|
|
645
|
+
</div>
|
|
646
|
+
{#each resultsSkeletonColumns as col (col)}
|
|
647
|
+
<div class="px-2 py-2">
|
|
648
|
+
<div class="h-3 rounded bg-bg-2 motion-safe:animate-pulse" style={`width: ${col === 0 ? 48 : 64 + ((row + col) % 4) * 18}px`}></div>
|
|
649
|
+
</div>
|
|
650
|
+
{/each}
|
|
651
|
+
</div>
|
|
652
|
+
{/each}
|
|
653
|
+
</div>
|
|
654
|
+
</div>
|
|
655
|
+
|
|
656
|
+
<div class="border-t border-line-1 px-3 py-1.5 flex items-center gap-3">
|
|
657
|
+
<div class="h-3 w-28 rounded bg-bg-2 motion-safe:animate-pulse"></div>
|
|
658
|
+
<div class="h-3 w-44 rounded bg-bg-2 motion-safe:animate-pulse"></div>
|
|
659
|
+
<div class="h-3 w-20 rounded bg-bg-2 motion-safe:animate-pulse"></div>
|
|
660
|
+
</div>
|
|
593
661
|
</div>
|
|
594
662
|
{:else}
|
|
595
663
|
{@const renderer = pickRenderer(tab, result)}
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
let error = $state<string | null>(null)
|
|
28
28
|
let search = $state('')
|
|
29
29
|
let capabilityFilter = $state<Capability | 'all'>('all')
|
|
30
|
+
const skeletonRows = Array.from({ length: 10 }, (_, i) => i)
|
|
31
|
+
const skeletonChips = Array.from({ length: 4 }, (_, i) => i)
|
|
30
32
|
|
|
31
33
|
const connected = $derived(connection.connected)
|
|
32
34
|
const activeUrl = $derived(connection.active.url)
|
|
@@ -113,7 +115,29 @@
|
|
|
113
115
|
<div class="mt-1 break-words">{error}</div>
|
|
114
116
|
</div>
|
|
115
117
|
{:else if loading && items.length === 0}
|
|
116
|
-
<div class="
|
|
118
|
+
<div class="schema-skeleton" aria-busy="true" aria-label="Loading schema">
|
|
119
|
+
<div class="p-2 border-b border-line-1 grid gap-2">
|
|
120
|
+
<div class="flex items-center gap-1.5 h-7 rounded border border-line-1 bg-bg-0 px-2">
|
|
121
|
+
<Search class="size-3 text-fg-3" />
|
|
122
|
+
<div class="h-3 w-32 rounded bg-bg-2 motion-safe:animate-pulse"></div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div class="flex flex-wrap gap-1">
|
|
126
|
+
{#each skeletonChips as chip (chip)}
|
|
127
|
+
<div class="h-5 rounded border border-line-1 bg-bg-1 px-1.5 motion-safe:animate-pulse" style={`width: ${chip === 0 ? 42 : 28 + chip * 8}px`}></div>
|
|
128
|
+
{/each}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<ul class="py-1" role="presentation">
|
|
133
|
+
{#each skeletonRows as row (row)}
|
|
134
|
+
<li class="flex h-7 items-center gap-2 px-3">
|
|
135
|
+
<div class="h-4 w-4 rounded bg-bg-2 motion-safe:animate-pulse"></div>
|
|
136
|
+
<div class="h-3 rounded bg-bg-2 motion-safe:animate-pulse" style={`width: ${row % 3 === 0 ? 132 : row % 3 === 1 ? 96 : 164}px`}></div>
|
|
137
|
+
</li>
|
|
138
|
+
{/each}
|
|
139
|
+
</ul>
|
|
140
|
+
</div>
|
|
117
141
|
{:else if items.length === 0}
|
|
118
142
|
<div class="p-3 flex flex-col items-start gap-2 text-[12px] text-fg-3 font-mono leading-relaxed">
|
|
119
143
|
<span class="text-fg-2">No user collections.</span>
|
|
@@ -4,22 +4,33 @@
|
|
|
4
4
|
import EmptyState from '$lib/EmptyState.svelte'
|
|
5
5
|
import { activity } from '$lib/activity.svelte'
|
|
6
6
|
import { connection } from '$lib/connections.svelte'
|
|
7
|
+
import type { PermissionDecision } from '$lib/permission-gate'
|
|
8
|
+
import {
|
|
9
|
+
POLICY_READ_CHECK,
|
|
10
|
+
assembleCapabilityMatrix,
|
|
11
|
+
capabilityMatrixChecksFromPolicies,
|
|
12
|
+
type CapabilityMatrixRow,
|
|
13
|
+
} from '$lib/policies'
|
|
7
14
|
import { secureStore } from '$lib/secureStore.svelte'
|
|
8
15
|
import type { AuthPolicy, AuthTenant, AuthUser, Whoami } from '#reddb'
|
|
9
|
-
import { Building2, Lock, RefreshCw, ScrollText,
|
|
16
|
+
import { Building2, CheckCircle2, Lock, RefreshCw, ScrollText, ShieldAlert, Users, XCircle } from 'lucide-svelte'
|
|
10
17
|
|
|
11
18
|
let whoami = $state<Whoami | null>(null)
|
|
12
19
|
let tenants = $state<AuthTenant[]>([])
|
|
13
20
|
let users = $state<AuthUser[]>([])
|
|
14
21
|
let policies = $state<AuthPolicy[]>([])
|
|
22
|
+
let policyReadDecision = $state<PermissionDecision | null>(null)
|
|
23
|
+
let capabilityRows = $state<CapabilityMatrixRow[]>([])
|
|
15
24
|
let loading = $state(false)
|
|
16
25
|
let whoamiError = $state<string | null>(null)
|
|
17
26
|
let tenantsError = $state<string | null>(null)
|
|
18
27
|
let usersError = $state<string | null>(null)
|
|
19
28
|
let policiesError = $state<string | null>(null)
|
|
29
|
+
let capabilityError = $state<string | null>(null)
|
|
20
30
|
|
|
21
31
|
const activeUrl = $derived(connection.active.url)
|
|
22
32
|
const connected = $derived(connection.connected)
|
|
33
|
+
const showPolicyPane = $derived(policyReadDecision?.allowed === true)
|
|
23
34
|
|
|
24
35
|
function fmtTime(v: number | undefined): string {
|
|
25
36
|
if (!v) return '-'
|
|
@@ -34,6 +45,8 @@
|
|
|
34
45
|
tenants = []
|
|
35
46
|
users = []
|
|
36
47
|
policies = []
|
|
48
|
+
policyReadDecision = null
|
|
49
|
+
capabilityRows = []
|
|
37
50
|
return
|
|
38
51
|
}
|
|
39
52
|
loading = true
|
|
@@ -41,20 +54,61 @@
|
|
|
41
54
|
tenantsError = null
|
|
42
55
|
usersError = null
|
|
43
56
|
policiesError = null
|
|
57
|
+
capabilityError = null
|
|
58
|
+
policyReadDecision = null
|
|
59
|
+
capabilityRows = []
|
|
44
60
|
try {
|
|
45
61
|
whoami = await activity.track('security · whoami', () => client.whoami())
|
|
46
62
|
} catch (e) {
|
|
47
63
|
whoamiError = (e as Error).message
|
|
48
64
|
whoami = null
|
|
49
65
|
}
|
|
66
|
+
try {
|
|
67
|
+
tenants = await activity.track('security · tenants', () => client.tenants())
|
|
68
|
+
} catch (e) {
|
|
69
|
+
tenantsError = (e as Error).message
|
|
70
|
+
tenants = []
|
|
71
|
+
}
|
|
50
72
|
try {
|
|
51
73
|
users = await activity.track('security · users', () => client.users())
|
|
52
74
|
} catch (e) {
|
|
53
75
|
usersError = (e as Error).message
|
|
54
76
|
users = []
|
|
55
|
-
} finally {
|
|
56
|
-
loading = false
|
|
57
77
|
}
|
|
78
|
+
try {
|
|
79
|
+
const [decision] = await activity.track('security · auth.can policy read', () => client.authCan([POLICY_READ_CHECK]))
|
|
80
|
+
policyReadDecision = decision ?? {
|
|
81
|
+
...POLICY_READ_CHECK,
|
|
82
|
+
allowed: false,
|
|
83
|
+
reason: 'auth.can omitted policy-read decision',
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
policyReadDecision = {
|
|
87
|
+
...POLICY_READ_CHECK,
|
|
88
|
+
allowed: false,
|
|
89
|
+
reason: (e as Error).message || 'auth.can failed',
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (policyReadDecision.allowed) {
|
|
93
|
+
try {
|
|
94
|
+
policies = await activity.track('security · policies', () => client.policies())
|
|
95
|
+
} catch (e) {
|
|
96
|
+
policiesError = (e as Error).message
|
|
97
|
+
policies = []
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const checks = capabilityMatrixChecksFromPolicies(policies)
|
|
101
|
+
const decisions = await activity.track('security · auth.can capability matrix', () => client.authCan(checks))
|
|
102
|
+
capabilityRows = assembleCapabilityMatrix(policies, decisions)
|
|
103
|
+
} catch (e) {
|
|
104
|
+
capabilityError = (e as Error).message
|
|
105
|
+
capabilityRows = []
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
policies = []
|
|
109
|
+
capabilityRows = []
|
|
110
|
+
}
|
|
111
|
+
loading = false
|
|
58
112
|
}
|
|
59
113
|
|
|
60
114
|
$effect(() => {
|
|
@@ -142,38 +196,40 @@
|
|
|
142
196
|
{/if}
|
|
143
197
|
</Card>
|
|
144
198
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
<div class="
|
|
162
|
-
<
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
199
|
+
{#if showPolicyPane}
|
|
200
|
+
<Card title="policies">
|
|
201
|
+
{#if policiesError}
|
|
202
|
+
<div class="mb-2 inline-flex items-center gap-1.5 rounded border border-warn/40 bg-bg-0 px-2 py-1 text-[11px] font-mono text-warn">
|
|
203
|
+
<ShieldAlert class="size-3.5" />
|
|
204
|
+
policies endpoint denied or unavailable
|
|
205
|
+
</div>
|
|
206
|
+
<pre class="whitespace-pre-wrap break-words text-[12px] font-mono text-fg-3">{policiesError}</pre>
|
|
207
|
+
{:else if policies.length === 0}
|
|
208
|
+
<div class="flex items-center gap-2 text-[12px] font-mono text-fg-3">
|
|
209
|
+
<ScrollText class="size-4" />
|
|
210
|
+
<span>No policies returned for this principal.</span>
|
|
211
|
+
</div>
|
|
212
|
+
{:else}
|
|
213
|
+
<div class="grid gap-1 text-[12px] font-mono">
|
|
214
|
+
{#each policies.slice(0, 6) as policy, i (`${policy.id ?? policy.name ?? i}`)}
|
|
215
|
+
<div class="rounded border border-line-1 bg-bg-0 px-2 py-1.5">
|
|
216
|
+
<div class="flex items-center gap-2">
|
|
217
|
+
<span class="text-fg-0">{policy.name ?? policy.id ?? 'policy'}</span>
|
|
218
|
+
{#if policy.effect}<Badge tone={policy.effect === 'deny' ? 'danger' : 'ok'}>{policy.effect}</Badge>{/if}
|
|
219
|
+
</div>
|
|
220
|
+
<div class="mt-1 text-fg-3">
|
|
221
|
+
{policy.action ?? '*'} · {policy.resource ?? '*'}
|
|
222
|
+
{#if policy.tenant} · tenant {policy.tenant}{/if}
|
|
223
|
+
</div>
|
|
168
224
|
</div>
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
225
|
+
{/each}
|
|
226
|
+
{#if policies.length > 6}
|
|
227
|
+
<div class="text-[11px] text-fg-3">{policies.length - 6} more policies in the table.</div>
|
|
228
|
+
{/if}
|
|
229
|
+
</div>
|
|
230
|
+
{/if}
|
|
231
|
+
</Card>
|
|
232
|
+
{/if}
|
|
177
233
|
</div>
|
|
178
234
|
|
|
179
235
|
<div class="grid gap-3 content-start">
|
|
@@ -217,38 +273,93 @@
|
|
|
217
273
|
{/if}
|
|
218
274
|
</Card>
|
|
219
275
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
<
|
|
228
|
-
<
|
|
229
|
-
<
|
|
230
|
-
<
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
</thead>
|
|
237
|
-
<tbody>
|
|
238
|
-
{#each policies as policy, i (`${policy.id ?? policy.name ?? i}`)}
|
|
239
|
-
<tr class="border-b border-line-1/60 hover:bg-bg-1/40">
|
|
240
|
-
<td class="px-2 py-1">{#if policy.effect}<Badge tone={policy.effect === 'deny' ? 'danger' : 'ok'}>{policy.effect}</Badge>{:else}<span class="text-fg-3">-</span>{/if}</td>
|
|
241
|
-
<td class="px-2 py-1 text-fg-1">{policy.principal ?? '-'}</td>
|
|
242
|
-
<td class="px-2 py-1 text-accent">{policy.action ?? '*'}</td>
|
|
243
|
-
<td class="px-2 py-1 text-fg-1">{policy.resource ?? '*'}</td>
|
|
244
|
-
<td class="px-2 py-1 text-fg-2">{policy.tenant ?? '-'}</td>
|
|
276
|
+
{#if showPolicyPane}
|
|
277
|
+
<Card title="policy table">
|
|
278
|
+
{#if policiesError}
|
|
279
|
+
<div class="text-[12px] font-mono text-fg-3">Policy table unavailable for this principal.</div>
|
|
280
|
+
{:else if policies.length === 0}
|
|
281
|
+
<div class="text-[12px] font-mono text-fg-3">No policies to tabulate.</div>
|
|
282
|
+
{:else}
|
|
283
|
+
<div class="overflow-auto">
|
|
284
|
+
<table class="w-full border-collapse text-[12px] font-mono">
|
|
285
|
+
<thead class="sticky top-0 bg-bg-1">
|
|
286
|
+
<tr class="border-b border-line-1 text-fg-3">
|
|
287
|
+
<th class="px-2 py-1.5 text-left font-normal">effect</th>
|
|
288
|
+
<th class="px-2 py-1.5 text-left font-normal">principal</th>
|
|
289
|
+
<th class="px-2 py-1.5 text-left font-normal">action</th>
|
|
290
|
+
<th class="px-2 py-1.5 text-left font-normal">resource</th>
|
|
291
|
+
<th class="px-2 py-1.5 text-left font-normal">tenant</th>
|
|
245
292
|
</tr>
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
293
|
+
</thead>
|
|
294
|
+
<tbody>
|
|
295
|
+
{#each policies as policy, i (`${policy.id ?? policy.name ?? i}`)}
|
|
296
|
+
<tr class="border-b border-line-1/60 hover:bg-bg-1/40">
|
|
297
|
+
<td class="px-2 py-1">{#if policy.effect}<Badge tone={policy.effect === 'deny' ? 'danger' : 'ok'}>{policy.effect}</Badge>{:else}<span class="text-fg-3">-</span>{/if}</td>
|
|
298
|
+
<td class="px-2 py-1 text-fg-1">{policy.principal ?? '-'}</td>
|
|
299
|
+
<td class="px-2 py-1 text-accent">{policy.action ?? '*'}</td>
|
|
300
|
+
<td class="px-2 py-1 text-fg-1">{policy.resource ?? '*'}</td>
|
|
301
|
+
<td class="px-2 py-1 text-fg-2">{policy.tenant ?? '-'}</td>
|
|
302
|
+
</tr>
|
|
303
|
+
{/each}
|
|
304
|
+
</tbody>
|
|
305
|
+
</table>
|
|
306
|
+
</div>
|
|
307
|
+
{/if}
|
|
308
|
+
</Card>
|
|
309
|
+
|
|
310
|
+
<Card title="my capabilities">
|
|
311
|
+
{#if capabilityError}
|
|
312
|
+
<div class="mb-2 inline-flex items-center gap-1.5 rounded border border-warn/40 bg-bg-0 px-2 py-1 text-[11px] font-mono text-warn">
|
|
313
|
+
<ShieldAlert class="size-3.5" />
|
|
314
|
+
capability matrix unavailable
|
|
315
|
+
</div>
|
|
316
|
+
<pre class="whitespace-pre-wrap break-words text-[12px] font-mono text-fg-3">{capabilityError}</pre>
|
|
317
|
+
{:else if capabilityRows.length === 0}
|
|
318
|
+
<div class="text-[12px] font-mono text-fg-3">No capability checks returned.</div>
|
|
319
|
+
{:else}
|
|
320
|
+
<div class="overflow-auto">
|
|
321
|
+
<table class="w-full border-collapse text-[12px] font-mono">
|
|
322
|
+
<thead class="sticky top-0 bg-bg-1">
|
|
323
|
+
<tr class="border-b border-line-1 text-fg-3">
|
|
324
|
+
<th class="px-2 py-1.5 text-left font-normal">can</th>
|
|
325
|
+
<th class="px-2 py-1.5 text-left font-normal">action</th>
|
|
326
|
+
<th class="px-2 py-1.5 text-left font-normal">resource</th>
|
|
327
|
+
<th class="px-2 py-1.5 text-left font-normal">policy effect</th>
|
|
328
|
+
<th class="px-2 py-1.5 text-left font-normal">reason</th>
|
|
329
|
+
</tr>
|
|
330
|
+
</thead>
|
|
331
|
+
<tbody>
|
|
332
|
+
{#each capabilityRows as row (row.id)}
|
|
333
|
+
<tr class="border-b border-line-1/60 hover:bg-bg-1/40">
|
|
334
|
+
<td class="px-2 py-1">
|
|
335
|
+
{#if row.allowed}
|
|
336
|
+
<span class="inline-flex items-center gap-1 text-ok"><CheckCircle2 class="size-3.5" /> yes</span>
|
|
337
|
+
{:else}
|
|
338
|
+
<span class="inline-flex items-center gap-1 text-danger"><XCircle class="size-3.5" /> no</span>
|
|
339
|
+
{/if}
|
|
340
|
+
</td>
|
|
341
|
+
<td class="px-2 py-1 text-accent">{row.action}</td>
|
|
342
|
+
<td class="px-2 py-1 text-fg-1">{row.resourceLabel}</td>
|
|
343
|
+
<td class="px-2 py-1">
|
|
344
|
+
{#if row.effects.length === 0}
|
|
345
|
+
<span class="text-fg-3">-</span>
|
|
346
|
+
{:else}
|
|
347
|
+
<span class="inline-flex flex-wrap gap-1">
|
|
348
|
+
{#each row.effects as effect}
|
|
349
|
+
<Badge tone={effect === 'deny' ? 'danger' : 'ok'}>{effect}</Badge>
|
|
350
|
+
{/each}
|
|
351
|
+
</span>
|
|
352
|
+
{/if}
|
|
353
|
+
</td>
|
|
354
|
+
<td class="max-w-[320px] px-2 py-1 text-fg-3">{row.reason ?? '-'}</td>
|
|
355
|
+
</tr>
|
|
356
|
+
{/each}
|
|
357
|
+
</tbody>
|
|
358
|
+
</table>
|
|
359
|
+
</div>
|
|
360
|
+
{/if}
|
|
361
|
+
</Card>
|
|
362
|
+
{/if}
|
|
252
363
|
</div>
|
|
253
364
|
</div>
|
|
254
365
|
{/if}
|