+241
-327
Diff
round #0
+1
-1
frontend/src/App.svelte
+1
-1
frontend/src/App.svelte
+1
-1
frontend/src/components/HandleInput.svelte
+1
-1
frontend/src/components/HandleInput.svelte
+226
-5
frontend/src/components/dashboard/ControllersContent.svelte
+226
-5
frontend/src/components/dashboard/ControllersContent.svelte
···
1
1
<script lang="ts">
2
-
import { onMount } from 'svelte'
2
+
import { onMount, onDestroy } from 'svelte'
3
3
import { _ } from '../../lib/i18n'
4
4
import { api } from '../../lib/api'
5
5
import { toast } from '../../lib/toast.svelte'
6
6
import { formatDateTime } from '../../lib/date'
7
-
import { routes, getFullUrl } from '../../lib/router.svelte'
8
-
import type { Session, DelegationController, DelegationControlledAccount, DelegationScopePreset } from '../../lib/types/api'
7
+
import type { Session, DelegationController, DelegationControlledAccount, DelegationScopePreset, DelegationAuditEntry } from '../../lib/types/api'
9
8
import { unsafeAsDid, unsafeAsScopeSet, unsafeAsHandle, unsafeAsEmail } from '../../lib/types/branded'
10
9
import type { Did, Handle, ScopeSet } from '../../lib/types/branded'
10
+
import LoadMoreSentinel from '../LoadMoreSentinel.svelte'
11
11
12
12
interface Props {
13
13
session: Session
···
123
123
let creatingDelegated = $state(false)
124
124
125
125
onMount(async () => {
126
-
await loadData()
126
+
await Promise.all([loadData(), loadAuditLog()])
127
+
pollInterval = setInterval(pollAuditLog, 15_000)
127
128
})
128
129
129
130
async function loadData() {
···
227
228
if ((scopes as string) === '') return $_('delegation.scopeViewer')
228
229
return $_('delegation.scopeCustom')
229
230
}
231
+
232
+
interface AuditEntry {
233
+
id: string
234
+
delegatedDid: string
235
+
actorDid: string
236
+
actionType: string
237
+
actionDetails: Record<string, unknown> | null
238
+
createdAt: string
239
+
}
240
+
241
+
let auditLoading = $state(true)
242
+
let auditLoadingMore = $state(false)
243
+
let auditEntries = $state<AuditEntry[]>([])
244
+
let auditHasMore = $state(true)
245
+
let auditOffset = $state(0)
246
+
const auditLimit = 20
247
+
let pollInterval: ReturnType<typeof setInterval> | null = null
248
+
249
+
onDestroy(() => {
250
+
if (pollInterval) clearInterval(pollInterval)
251
+
})
252
+
253
+
async function loadAuditLog() {
254
+
auditLoading = true
255
+
auditOffset = 0
256
+
try {
257
+
const result = await api.getDelegationAuditLog(session.accessJwt, auditLimit, 0)
258
+
if (result.ok && result.value) {
259
+
const rawEntries = Array.isArray(result.value.entries) ? result.value.entries : []
260
+
auditEntries = rawEntries.map(mapAuditEntry)
261
+
const total = result.value.total ?? 0
262
+
auditHasMore = auditEntries.length < total
263
+
auditOffset = auditEntries.length
264
+
} else {
265
+
auditEntries = []
266
+
auditHasMore = false
267
+
}
268
+
} catch {
269
+
toast.error($_('delegation.failedToLoadAudit'))
270
+
auditEntries = []
271
+
auditHasMore = false
272
+
} finally {
273
+
auditLoading = false
274
+
}
275
+
}
276
+
277
+
async function pollAuditLog() {
278
+
try {
279
+
const result = await api.getDelegationAuditLog(session.accessJwt, auditLimit, 0)
280
+
if (result.ok && result.value) {
281
+
const rawEntries = Array.isArray(result.value.entries) ? result.value.entries : []
282
+
const latest = rawEntries.map(mapAuditEntry)
283
+
if (latest.length > 0 && (auditEntries.length === 0 || latest[0].id !== auditEntries[0].id)) {
284
+
auditEntries = latest
285
+
const total = result.value.total ?? 0
286
+
auditHasMore = auditEntries.length < total
287
+
auditOffset = auditEntries.length
288
+
}
289
+
} else if (result.ok === false) {
290
+
if (pollInterval) { clearInterval(pollInterval); pollInterval = null }
291
+
}
292
+
} catch {
293
+
if (pollInterval) { clearInterval(pollInterval); pollInterval = null }
294
+
}
295
+
}
296
+
297
+
async function loadMoreAuditEntries() {
298
+
if (auditLoadingMore || !auditHasMore) return
299
+
auditLoadingMore = true
300
+
try {
301
+
const result = await api.getDelegationAuditLog(session.accessJwt, auditLimit, auditOffset)
302
+
if (result.ok && result.value) {
303
+
const rawEntries = Array.isArray(result.value.entries) ? result.value.entries : []
304
+
const newEntries = rawEntries.map(mapAuditEntry)
305
+
auditEntries = [...auditEntries, ...newEntries]
306
+
const total = result.value.total ?? 0
307
+
auditHasMore = auditEntries.length < total
308
+
auditOffset = auditEntries.length
309
+
}
310
+
} catch {
311
+
toast.error($_('delegation.failedToLoadAudit'))
312
+
} finally {
313
+
auditLoadingMore = false
314
+
}
315
+
}
316
+
317
+
function mapAuditEntry(e: DelegationAuditEntry): AuditEntry {
318
+
const parsed: Record<string, unknown> | null = e.details
319
+
? (() => { try { return JSON.parse(e.details!) as Record<string, unknown> } catch { return null } })()
320
+
: null
321
+
return {
322
+
id: e.id,
323
+
delegatedDid: e.target_did ?? '',
324
+
actorDid: e.actor_did,
325
+
actionType: e.action,
326
+
actionDetails: parsed,
327
+
createdAt: e.created_at,
328
+
}
329
+
}
330
+
331
+
function formatActionType(type: string): string {
332
+
const labels: Record<string, string> = {
333
+
'GrantCreated': $_('delegation.actionGrantCreated'),
334
+
'GrantRevoked': $_('delegation.actionGrantRevoked'),
335
+
'ScopesModified': $_('delegation.actionScopesModified'),
336
+
'TokenIssued': $_('delegation.actionTokenIssued'),
337
+
'RepoWrite': $_('delegation.actionRepoWrite'),
338
+
'BlobUpload': $_('delegation.actionBlobUpload'),
339
+
'AccountAction': $_('delegation.actionAccountAction'),
340
+
}
341
+
return labels[type] || type
342
+
}
343
+
344
+
function formatActionDetails(details: Record<string, unknown> | null): string {
345
+
if (!details) return ''
346
+
return Object.entries(details)
347
+
.map(([key, value]) => `${key.replace(/_/g, ' ')}: ${JSON.stringify(value)}`)
348
+
.join(', ')
349
+
}
350
+
230
351
</script>
231
352
232
353
<div class="controllers">
···
454
575
{$_('delegation.createDelegatedAccountButton')}
455
576
</button>
456
577
{/if}
578
+
579
+
<div class="constraint-notice">
580
+
<p>{$_('delegation.controlledAccountsLocalOnly')}</p>
581
+
</div>
457
582
</section>
458
583
459
584
<section class="section">
···
461
586
<h3>{$_('delegation.auditLog')}</h3>
462
587
<p class="section-description">{$_('delegation.auditLogDesc')}</p>
463
588
</div>
464
-
<a href={getFullUrl(routes.delegationAudit)} class="btn-link">{$_('delegation.viewAuditLog')}</a>
589
+
590
+
{#if auditLoading}
591
+
<div class="loading">{$_('common.loading')}</div>
592
+
{:else if auditEntries.length === 0}
593
+
<p class="empty">{$_('delegation.noAuditEntries')}</p>
594
+
{:else}
595
+
<div class="audit-entries">
596
+
{#each auditEntries as entry}
597
+
<div class="audit-entry">
598
+
<div class="audit-entry-header">
599
+
<span class="action-type">{formatActionType(entry.actionType)}</span>
600
+
<span class="audit-entry-date">{formatDateTime(entry.createdAt)}</span>
601
+
</div>
602
+
<div class="audit-entry-details">
603
+
<div class="detail">
604
+
<span class="label">{$_('delegation.actor')}</span>
605
+
<span class="value did">{entry.actorDid}</span>
606
+
</div>
607
+
{#if entry.delegatedDid}
608
+
<div class="detail">
609
+
<span class="label">{$_('delegation.target')}</span>
610
+
<span class="value did">{entry.delegatedDid}</span>
611
+
</div>
612
+
{/if}
613
+
{#if entry.actionDetails}
614
+
<div class="detail">
615
+
<span class="label">{$_('delegation.details')}</span>
616
+
<span class="value audit-details-value">{formatActionDetails(entry.actionDetails)}</span>
617
+
</div>
618
+
{/if}
619
+
</div>
620
+
</div>
621
+
{/each}
622
+
</div>
623
+
624
+
<LoadMoreSentinel hasMore={auditHasMore} loading={auditLoadingMore} onLoadMore={loadMoreAuditEntries} />
625
+
{/if}
465
626
</section>
466
627
{/if}
467
628
</div>
···
505
666
border: 1px solid var(--border-color);
506
667
border-radius: var(--radius-md);
507
668
padding: var(--space-4);
669
+
margin-top: var(--space-4);
508
670
}
509
671
510
672
.constraint-notice p {
···
846
1008
border: 1px solid var(--info-border, var(--border-color));
847
1009
}
848
1010
1011
+
.audit-entries {
1012
+
display: flex;
1013
+
flex-direction: column;
1014
+
gap: var(--space-3);
1015
+
}
1016
+
1017
+
.audit-entry {
1018
+
background: var(--bg-card);
1019
+
border: 1px solid var(--border-color);
1020
+
border-radius: var(--radius-lg);
1021
+
padding: var(--space-4);
1022
+
}
1023
+
1024
+
.audit-entry-header {
1025
+
display: flex;
1026
+
justify-content: space-between;
1027
+
align-items: center;
1028
+
margin-bottom: var(--space-3);
1029
+
}
1030
+
1031
+
.action-type {
1032
+
font-weight: var(--font-medium);
1033
+
padding: var(--space-1) var(--space-2);
1034
+
background: var(--accent);
1035
+
color: var(--text-inverse);
1036
+
border-radius: var(--radius-md);
1037
+
font-size: var(--text-sm);
1038
+
}
1039
+
1040
+
.audit-entry-date {
1041
+
font-size: var(--text-sm);
1042
+
color: var(--text-secondary);
1043
+
}
1044
+
1045
+
.audit-entry-details {
1046
+
display: flex;
1047
+
flex-direction: column;
1048
+
gap: var(--space-2);
1049
+
}
1050
+
1051
+
.audit-details-value {
1052
+
font-family: var(--font-mono);
1053
+
font-size: var(--text-xs);
1054
+
word-break: break-word;
1055
+
}
1056
+
849
1057
@media (max-width: 600px) {
850
1058
.item-card {
851
1059
flex-direction: column;
···
861
1069
width: 100%;
862
1070
text-align: center;
863
1071
}
1072
+
1073
+
.audit-entry-header {
1074
+
flex-direction: column;
1075
+
align-items: flex-start;
1076
+
gap: var(--space-2);
1077
+
}
1078
+
1079
+
.audit-entry-details .value.did {
1080
+
overflow: hidden;
1081
+
text-overflow: ellipsis;
1082
+
white-space: nowrap;
1083
+
max-width: 60vw;
1084
+
}
864
1085
}
865
1086
</style>
-282
frontend/src/components/dashboard/DelegationAuditContent.svelte
-282
frontend/src/components/dashboard/DelegationAuditContent.svelte
···
1
-
<script lang="ts">
2
-
import { onMount } from 'svelte'
3
-
import { _ } from '../../lib/i18n'
4
-
import { api } from '../../lib/api'
5
-
import { toast } from '../../lib/toast.svelte'
6
-
import { formatDateTime } from '../../lib/date'
7
-
import type { Session, DelegationAuditEntry } from '../../lib/types/api'
8
-
import LoadMoreSentinel from '../LoadMoreSentinel.svelte'
9
-
10
-
interface Props {
11
-
session: Session
12
-
}
13
-
14
-
let { session }: Props = $props()
15
-
16
-
interface AuditEntry {
17
-
id: string
18
-
delegatedDid: string
19
-
actorDid: string
20
-
controllerDid: string | null
21
-
actionType: string
22
-
actionDetails: Record<string, unknown> | null
23
-
createdAt: string
24
-
}
25
-
26
-
let loading = $state(true)
27
-
let loadingMore = $state(false)
28
-
let entries = $state<AuditEntry[]>([])
29
-
let hasMore = $state(true)
30
-
let offset = $state(0)
31
-
const limit = 20
32
-
33
-
onMount(async () => {
34
-
await loadAuditLog()
35
-
})
36
-
37
-
async function loadAuditLog() {
38
-
loading = true
39
-
offset = 0
40
-
try {
41
-
const result = await api.getDelegationAuditLog(session.accessJwt, limit, 0)
42
-
if (result.ok && result.value) {
43
-
const rawEntries = Array.isArray(result.value.entries) ? result.value.entries : []
44
-
entries = rawEntries.map(mapEntry)
45
-
const total = result.value.total ?? 0
46
-
hasMore = entries.length < total
47
-
offset = entries.length
48
-
} else {
49
-
entries = []
50
-
hasMore = false
51
-
}
52
-
} catch {
53
-
toast.error($_('delegation.failedToLoadAudit'))
54
-
entries = []
55
-
hasMore = false
56
-
} finally {
57
-
loading = false
58
-
}
59
-
}
60
-
61
-
async function loadMoreEntries() {
62
-
if (loadingMore || !hasMore) return
63
-
loadingMore = true
64
-
try {
65
-
const result = await api.getDelegationAuditLog(session.accessJwt, limit, offset)
66
-
if (result.ok && result.value) {
67
-
const rawEntries = Array.isArray(result.value.entries) ? result.value.entries : []
68
-
const newEntries = rawEntries.map(mapEntry)
69
-
entries = [...entries, ...newEntries]
70
-
const total = result.value.total ?? 0
71
-
hasMore = entries.length < total
72
-
offset = entries.length
73
-
}
74
-
} catch {
75
-
toast.error($_('delegation.failedToLoadAudit'))
76
-
} finally {
77
-
loadingMore = false
78
-
}
79
-
}
80
-
81
-
function mapEntry(e: DelegationAuditEntry): AuditEntry {
82
-
return {
83
-
id: e.id,
84
-
delegatedDid: e.target_did ?? '',
85
-
actorDid: e.actor_did,
86
-
controllerDid: null,
87
-
actionType: e.action,
88
-
actionDetails: e.details ? JSON.parse(e.details) : null,
89
-
createdAt: e.created_at
90
-
}
91
-
}
92
-
93
-
function formatActionType(type: string): string {
94
-
const labels: Record<string, string> = {
95
-
'GrantCreated': $_('delegation.actionGrantCreated'),
96
-
'GrantRevoked': $_('delegation.actionGrantRevoked'),
97
-
'ScopesModified': $_('delegation.actionScopesModified'),
98
-
'TokenIssued': $_('delegation.actionTokenIssued'),
99
-
'RepoWrite': $_('delegation.actionRepoWrite'),
100
-
'BlobUpload': $_('delegation.actionBlobUpload'),
101
-
'AccountAction': $_('delegation.actionAccountAction')
102
-
}
103
-
return labels[type] || type
104
-
}
105
-
106
-
function formatActionDetails(details: Record<string, unknown> | null): string {
107
-
if (!details) return ''
108
-
return Object.entries(details)
109
-
.map(([key, value]) => `${key.replace(/_/g, ' ')}: ${JSON.stringify(value)}`)
110
-
.join(', ')
111
-
}
112
-
113
-
function truncateDid(did: string): string {
114
-
if (did.length <= 30) return did
115
-
return did.substring(0, 20) + '...' + did.substring(did.length - 6)
116
-
}
117
-
</script>
118
-
119
-
<div class="audit">
120
-
<div class="actions-bar">
121
-
<button type="button" class="ghost" onclick={() => loadAuditLog()} disabled={loading}>
122
-
{loading ? $_('common.loading') : $_('delegation.refresh')}
123
-
</button>
124
-
</div>
125
-
126
-
{#if loading}
127
-
<div class="loading">{$_('common.loading')}</div>
128
-
{:else if entries.length === 0}
129
-
<p class="empty">{$_('delegation.noAuditEntries')}</p>
130
-
{:else}
131
-
<div class="entries">
132
-
{#each entries as entry}
133
-
<div class="entry">
134
-
<div class="entry-header">
135
-
<span class="action-type">{formatActionType(entry.actionType)}</span>
136
-
<span class="entry-date">{formatDateTime(entry.createdAt)}</span>
137
-
</div>
138
-
<div class="entry-details">
139
-
<div class="detail">
140
-
<span class="label">{$_('delegation.actor')}</span>
141
-
<span class="value did" title={entry.actorDid}>{truncateDid(entry.actorDid)}</span>
142
-
</div>
143
-
{#if entry.delegatedDid}
144
-
<div class="detail">
145
-
<span class="label">{$_('delegation.target')}</span>
146
-
<span class="value did" title={entry.delegatedDid}>{truncateDid(entry.delegatedDid)}</span>
147
-
</div>
148
-
{/if}
149
-
{#if entry.actionDetails}
150
-
<div class="detail">
151
-
<span class="label">{$_('delegation.details')}</span>
152
-
<span class="value details">{formatActionDetails(entry.actionDetails)}</span>
153
-
</div>
154
-
{/if}
155
-
</div>
156
-
</div>
157
-
{/each}
158
-
</div>
159
-
160
-
<LoadMoreSentinel {hasMore} loading={loadingMore} onLoadMore={loadMoreEntries} />
161
-
{/if}
162
-
</div>
163
-
164
-
<style>
165
-
.audit {
166
-
max-width: var(--width-lg);
167
-
}
168
-
169
-
.actions-bar {
170
-
display: flex;
171
-
justify-content: flex-end;
172
-
margin-bottom: var(--space-4);
173
-
}
174
-
175
-
.ghost {
176
-
background: transparent;
177
-
border: 1px solid var(--border-color);
178
-
color: var(--text-primary);
179
-
padding: var(--space-2) var(--space-4);
180
-
border-radius: var(--radius-md);
181
-
cursor: pointer;
182
-
}
183
-
184
-
.ghost:hover:not(:disabled) {
185
-
border-color: var(--accent);
186
-
}
187
-
188
-
.ghost:disabled {
189
-
opacity: 0.6;
190
-
cursor: not-allowed;
191
-
}
192
-
193
-
.loading,
194
-
.empty {
195
-
color: var(--text-secondary);
196
-
padding: var(--space-6);
197
-
text-align: center;
198
-
}
199
-
200
-
.entries {
201
-
display: flex;
202
-
flex-direction: column;
203
-
gap: var(--space-3);
204
-
}
205
-
206
-
.entry {
207
-
background: var(--bg-secondary);
208
-
border: 1px solid var(--border-color);
209
-
border-radius: var(--radius-lg);
210
-
padding: var(--space-4);
211
-
}
212
-
213
-
.entry-header {
214
-
display: flex;
215
-
justify-content: space-between;
216
-
align-items: center;
217
-
margin-bottom: var(--space-3);
218
-
}
219
-
220
-
.action-type {
221
-
font-weight: var(--font-medium);
222
-
padding: var(--space-1) var(--space-2);
223
-
background: var(--accent);
224
-
color: var(--text-inverse);
225
-
border-radius: var(--radius-md);
226
-
font-size: var(--text-sm);
227
-
}
228
-
229
-
.entry-date {
230
-
font-size: var(--text-sm);
231
-
color: var(--text-secondary);
232
-
}
233
-
234
-
.entry-details {
235
-
display: flex;
236
-
flex-direction: column;
237
-
gap: var(--space-2);
238
-
}
239
-
240
-
.detail {
241
-
display: flex;
242
-
gap: var(--space-2);
243
-
font-size: var(--text-sm);
244
-
}
245
-
246
-
.detail .label {
247
-
color: var(--text-secondary);
248
-
min-width: 60px;
249
-
}
250
-
251
-
.detail .value {
252
-
color: var(--text-primary);
253
-
}
254
-
255
-
.detail .value.did {
256
-
font-family: var(--font-mono);
257
-
font-size: var(--text-xs);
258
-
}
259
-
260
-
.detail .value.details {
261
-
font-family: var(--font-mono);
262
-
font-size: var(--text-xs);
263
-
word-break: break-word;
264
-
}
265
-
266
-
@media (max-width: 500px) {
267
-
.entry-header {
268
-
flex-direction: column;
269
-
align-items: flex-start;
270
-
gap: var(--space-2);
271
-
}
272
-
273
-
.detail {
274
-
flex-direction: column;
275
-
gap: var(--space-1);
276
-
}
277
-
278
-
.detail .label {
279
-
min-width: unset;
280
-
}
281
-
}
282
-
</style>
-5
frontend/src/components/dashboard/InviteCodesContent.svelte
-5
frontend/src/components/dashboard/InviteCodesContent.svelte
+1
-1
frontend/src/lib/types/routes.ts
+1
-1
frontend/src/lib/types/routes.ts
+1
-4
frontend/src/locales/en.json
+1
-4
frontend/src/locales/en.json
···
144
144
"navComms": "Communication Preferences",
145
145
"navRepo": "Repository Explorer",
146
146
"navDelegation": "Delegation",
147
-
"navDelegationAudit": "Delegation Audit",
148
147
"navAdmin": "Admin Panel",
149
148
"navDidDocument": "DID Document",
150
149
"migrated": "Migrated",
···
836
835
"controllerRemoved": "Controller removed successfully",
837
836
"controlledAccounts": "Controlled Accounts",
838
837
"controlledAccountsDesc": "Accounts you can act on behalf of",
838
+
"controlledAccountsLocalOnly": "Only accounts on this PDS are shown. Another PDS may have granted you controller access separately",
839
839
"noControlledAccounts": "You do not have access to any delegated accounts.",
840
840
"actAs": "Act As",
841
841
"cannotControlAccounts": "You cannot control other accounts because this account has controllers. An account can either have controllers or control other accounts, but not both.",
···
848
848
"accountCreated": "Created delegated account: {handle}",
849
849
"auditLog": "Audit Log",
850
850
"auditLogDesc": "View all delegation activity",
851
-
"viewAuditLog": "View Audit Log",
852
851
"scopeOwner": "Owner",
853
852
"scopeViewer": "Viewer",
854
853
"scopeCustom": "Custom",
···
856
855
"details": "Details",
857
856
"previous": "Previous",
858
857
"next": "Next",
859
-
"refresh": "Refresh",
860
858
"actionGrantCreated": "Grant Created",
861
859
"actionGrantRevoked": "Grant Revoked",
862
860
"actionScopesModified": "Scopes Modified",
···
866
864
"actionAccountAction": "Account Action",
867
865
"noAuditEntries": "No audit entries",
868
866
"target": "Target",
869
-
"pageInfo": "{start} - {end} of {total}",
870
867
"failedToLoadAudit": "Failed to load audit log"
871
868
},
872
869
"actAs": {
+1
-4
frontend/src/locales/fi.json
+1
-4
frontend/src/locales/fi.json
···
124
124
"dashboard": {
125
125
"title": "Hallintapaneeli",
126
126
"accountManager": "Tilinhallinta",
127
-
"navDelegationAudit": "Delegointiloki",
128
127
"switchAccount": "Vaihda tiliรค",
129
128
"addAnotherAccount": "Lisรครค toinen tili",
130
129
"signOut": "Kirjaudu ulos @{handle}",
···
835
834
"controllerRemoved": "Hallinnoija poistettu",
836
835
"controlledAccounts": "Hallinnoidut tilit",
837
836
"controlledAccountsDesc": "Tilit, joiden puolesta voit toimia",
837
+
"controlledAccountsLocalOnly": "Vain tรคmรคn PDS:n tilit nรคytetรครคn. Toinen PDS on voinut myรถntรครค sinulle ohjausoikeuden erikseen",
838
838
"noControlledAccounts": "Sinulla ei ole pรครคsyรค delegoituihin tileihin.",
839
839
"actAs": "Toimi kรคyttรคjรคnรค",
840
840
"cannotControlAccounts": "Et voi hallinnoida muita tilejรค, koska tรคllรค tilillรค on hallinnoijia. Tili voi joko olla hallinnoija tai hallinnoidaan, mutta ei molempia.",
···
847
847
"accountCreated": "Delegoitu tili luotu: {handle}",
848
848
"auditLog": "Tapahtumaloki",
849
849
"auditLogDesc": "Nรคytรค kaikki delegointitoiminta",
850
-
"viewAuditLog": "Nรคytรค tapahtumaloki",
851
850
"scopeOwner": "Omistaja",
852
851
"scopeViewer": "Katsoja",
853
852
"scopeCustom": "Mukautettu",
···
855
854
"details": "Tiedot",
856
855
"previous": "Edellinen",
857
856
"next": "Seuraava",
858
-
"refresh": "Pรคivitรค",
859
857
"actionGrantCreated": "Oikeus luotu",
860
858
"actionGrantRevoked": "Oikeus peruttu",
861
859
"actionScopesModified": "Oikeuksia muokattu",
···
865
863
"actionAccountAction": "Tilitoiminto",
866
864
"noAuditEntries": "Ei lokimerkintรถjรค",
867
865
"target": "Kohde",
868
-
"pageInfo": "{start} - {end} / {total}",
869
866
"failedToLoadAudit": "Lokin lataus epรคonnistui"
870
867
},
871
868
"actAs": {
+1
-4
frontend/src/locales/ja.json
+1
-4
frontend/src/locales/ja.json
···
124
124
"dashboard": {
125
125
"title": "ใใใทใฅใใผใ",
126
126
"accountManager": "ใขใซใฆใณใ็ฎก็",
127
-
"navDelegationAudit": "ๅงไปป็ฃๆป",
128
127
"switchAccount": "ใขใซใฆใณใๅๆฟ",
129
128
"addAnotherAccount": "ๅฅใฎใขใซใฆใณใใ่ฟฝๅ ",
130
129
"signOut": "@{handle} ใใใตใคใณใขใฆใ",
···
834
833
"actionAccountAction": "ใขใซใฆใณใใขใฏใทใงใณ",
835
834
"previous": "ๅใธ",
836
835
"next": "ๆฌกใธ",
837
-
"refresh": "ๆดๆฐ",
838
836
"adding": "่ฟฝๅ ไธญ...",
839
837
"accessLevel": "ใขใฏใปในใฌใใซ",
840
838
"addControllerButton": "+ ใณใณใใญใผใฉใผใ่ฟฝๅ ",
···
848
846
"cannotAddControllers": "ไปใฎใขใซใฆใณใใ็ฎก็ใใฆใใใใใใณใณใใญใผใฉใผใ่ฟฝๅ ใงใใพใใใใขใซใฆใณใใฏใณใณใใญใผใฉใผใๆใคใใไปใฎใขใซใฆใณใใ็ฎก็ใใใใฎใใใใใฎใฟๅฏ่ฝใงใใ",
849
847
"cannotControlAccounts": "ใใฎใขใซใฆใณใใซใฏใณใณใใญใผใฉใผใใใใใใไปใฎใขใซใฆใณใใ็ฎก็ใงใใพใใใใขใซใฆใณใใฏใณใณใใญใผใฉใผใๆใคใใไปใฎใขใซใฆใณใใ็ฎก็ใใใใฎใใใใใฎใฟๅฏ่ฝใงใใ",
850
848
"controlledAccountsDesc": "ใใชใใไปฃใใใซๆไฝใงใใใขใซใฆใณใ",
849
+
"controlledAccountsLocalOnly": "ใใฎPDSไธใฎใขใซใฆใณใใฎใฟ่กจ็คบใใใพใใๅฅใฎPDSใใณใณใใญใผใฉใผใขใฏใปในใๅๅฅใซไปไธใใฆใใๅ ดๅใใใใพใ",
851
850
"controllerAdded": "ใณใณใใญใผใฉใผใ่ฟฝๅ ใใพใใ",
852
851
"controllerDid": "ใณใณใใญใผใฉใผDID",
853
852
"controllerRemoved": "ใณใณใใญใผใฉใผใๅ้คใใพใใ",
···
860
859
"inactive": "้ใขใฏใใฃใ",
861
860
"remove": "ๅ้ค",
862
861
"removeConfirm": "ใใฎใณใณใใญใผใฉใผใๅ้คใใพใใ๏ผ",
863
-
"viewAuditLog": "็ฃๆปใญใฐใ่กจ็คบ",
864
862
"yourAccessLevel": "ใใชใใฎใขใฏใปในใฌใใซ",
865
863
"accountCreated": "ๅงไปปใขใซใฆใณใใไฝๆใใพใใ: {handle}",
866
864
"noAuditEntries": "็ฃๆปใจใณใใชใชใ",
867
865
"target": "ๅฏพ่ฑก",
868
-
"pageInfo": "{start} - {end} / {total}",
869
866
"failedToLoadAudit": "็ฃๆปใญใฐใฎ่ชญใฟ่พผใฟใซๅคฑๆใใพใใ"
870
867
},
871
868
"actAs": {
+1
-4
frontend/src/locales/ko.json
+1
-4
frontend/src/locales/ko.json
···
124
124
"dashboard": {
125
125
"title": "๋์๋ณด๋",
126
126
"accountManager": "๊ณ์ ๊ด๋ฆฌ",
127
-
"navDelegationAudit": "์์ ๊ฐ์ฌ",
128
127
"switchAccount": "๊ณ์ ์ ํ",
129
128
"addAnotherAccount": "๋ค๋ฅธ ๊ณ์ ์ถ๊ฐ",
130
129
"signOut": "@{handle} ๋ก๊ทธ์์",
···
834
833
"actionAccountAction": "๊ณ์ ์์
",
835
834
"previous": "์ด์ ",
836
835
"next": "๋ค์",
837
-
"refresh": "์๋ก๊ณ ์นจ",
838
836
"adding": "์ถ๊ฐ ์ค...",
839
837
"accessLevel": "์ก์ธ์ค ์์ค",
840
838
"addControllerButton": "+ ์ปจํธ๋กค๋ฌ ์ถ๊ฐ",
···
848
846
"cannotAddControllers": "๋ค๋ฅธ ๊ณ์ ์ ๊ด๋ฆฌํ๊ณ ์์ด ์ปจํธ๋กค๋ฌ๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค. ๊ณ์ ์ ์ปจํธ๋กค๋ฌ๋ฅผ ๊ฐ์ง๊ฑฐ๋ ๋ค๋ฅธ ๊ณ์ ์ ๊ด๋ฆฌํ ์ ์์ง๋ง ๋ ๋ค๋ ๋ถ๊ฐ๋ฅํฉ๋๋ค.",
849
847
"cannotControlAccounts": "์ด ๊ณ์ ์ ์ปจํธ๋กค๋ฌ๊ฐ ์์ด ๋ค๋ฅธ ๊ณ์ ์ ๊ด๋ฆฌํ ์ ์์ต๋๋ค. ๊ณ์ ์ ์ปจํธ๋กค๋ฌ๋ฅผ ๊ฐ์ง๊ฑฐ๋ ๋ค๋ฅธ ๊ณ์ ์ ๊ด๋ฆฌํ ์ ์์ง๋ง ๋ ๋ค๋ ๋ถ๊ฐ๋ฅํฉ๋๋ค.",
850
848
"controlledAccountsDesc": "๊ทํ๊ฐ ๋์ ์์
ํ ์ ์๋ ๊ณ์ ",
849
+
"controlledAccountsLocalOnly": "์ด PDS์ ๊ณ์ ๋ง ํ์๋ฉ๋๋ค. ๋ค๋ฅธ PDS์์ ๋ณ๋๋ก ์ปจํธ๋กค๋ฌ ์ก์ธ์ค๋ฅผ ๋ถ์ฌํ์ ์ ์์ต๋๋ค",
851
850
"controllerAdded": "์ปจํธ๋กค๋ฌ๊ฐ ์ถ๊ฐ๋์์ต๋๋ค",
852
851
"controllerDid": "์ปจํธ๋กค๋ฌ DID",
853
852
"controllerRemoved": "์ปจํธ๋กค๋ฌ๊ฐ ์ ๊ฑฐ๋์์ต๋๋ค",
···
860
859
"inactive": "๋นํ์ฑ",
861
860
"remove": "์ ๊ฑฐ",
862
861
"removeConfirm": "์ด ์ปจํธ๋กค๋ฌ๋ฅผ ์ ๊ฑฐํ์๊ฒ ์ต๋๊น?",
863
-
"viewAuditLog": "๊ฐ์ฌ ๋ก๊ทธ ๋ณด๊ธฐ",
864
862
"yourAccessLevel": "๊ทํ์ ์ก์ธ์ค ์์ค",
865
863
"accountCreated": "์์ ๊ณ์ ์ด ์์ฑ๋์์ต๋๋ค: {handle}",
866
864
"noAuditEntries": "๊ฐ์ฌ ํญ๋ชฉ ์์",
867
865
"target": "๋์",
868
-
"pageInfo": "{start} - {end} / {total}",
869
866
"failedToLoadAudit": "๊ฐ์ฌ ๋ก๊ทธ ๋ก๋ฉ ์คํจ"
870
867
},
871
868
"actAs": {
+1
-4
frontend/src/locales/sv.json
+1
-4
frontend/src/locales/sv.json
···
124
124
"dashboard": {
125
125
"title": "Kontrollpanel",
126
126
"accountManager": "Kontohantering",
127
-
"navDelegationAudit": "Delegeringsgranskning",
128
127
"switchAccount": "Byt konto",
129
128
"addAnotherAccount": "Lรคgg till ett annat konto",
130
129
"signOut": "Logga ut @{handle}",
···
834
833
"actionAccountAction": "Kontoรฅtgรคrd",
835
834
"previous": "Fรถregรฅende",
836
835
"next": "Nรคsta",
837
-
"refresh": "Uppdatera",
838
836
"adding": "Lรคgger till...",
839
837
"accessLevel": "ร
tkomstnivรฅ",
840
838
"addControllerButton": "+ Lรคgg till kontrollant",
···
848
846
"cannotAddControllers": "Du kan inte lรคgga till kontrollanter eftersom detta konto kontrollerar andra konton. Ett konto kan antingen ha kontrollanter eller kontrollera andra konton, men inte bรฅda.",
849
847
"cannotControlAccounts": "Du kan inte kontrollera andra konton eftersom detta konto har kontrollanter. Ett konto kan antingen ha kontrollanter eller kontrollera andra konton, men inte bรฅda.",
850
848
"controlledAccountsDesc": "Konton du kan agera fรถr",
849
+
"controlledAccountsLocalOnly": "Endast konton pรฅ denna PDS visas. En annan PDS kan ha beviljat dig kontrollรฅtkomst separat",
851
850
"controllerAdded": "Kontrollant tillagd",
852
851
"controllerDid": "Kontrollant-DID",
853
852
"controllerRemoved": "Kontrollant borttagen",
···
860
859
"inactive": "Inaktiv",
861
860
"remove": "Ta bort",
862
861
"removeConfirm": "Vill du ta bort denna kontrollant?",
863
-
"viewAuditLog": "Visa granskningslogg",
864
862
"yourAccessLevel": "Din รฅtkomstnivรฅ",
865
863
"accountCreated": "Skapade delegerat konto: {handle}",
866
864
"noAuditEntries": "Inga granskningsposter",
867
865
"target": "Mรฅl",
868
-
"pageInfo": "{start} - {end} av {total}",
869
866
"failedToLoadAudit": "Kunde inte ladda granskningslogg"
870
867
},
871
868
"actAs": {
+1
-4
frontend/src/locales/zh.json
+1
-4
frontend/src/locales/zh.json
···
124
124
"dashboard": {
125
125
"title": "ๆงๅถๅฐ",
126
126
"accountManager": "่ดฆๆท็ฎก็",
127
-
"navDelegationAudit": "ๅงๆๅฎก่ฎก",
128
127
"switchAccount": "ๅๆข่ดฆๆท",
129
128
"addAnotherAccount": "ๆทปๅ ๅ
ถไป่ดฆๆท",
130
129
"signOut": "้ๅบ @{handle}",
···
835
834
"actionAccountAction": "่ดฆๆทๆไฝ",
836
835
"previous": "ไธไธ้กต",
837
836
"next": "ไธไธ้กต",
838
-
"refresh": "ๅทๆฐ",
839
837
"adding": "ๆทปๅ ไธญ...",
840
838
"accessLevel": "่ฎฟ้ฎ็บงๅซ",
841
839
"addControllerButton": "+ ๆทปๅ ๆงๅถ่
",
···
849
847
"cannotAddControllers": "ๅ ไธบๆญค่ดฆๆทๆญฃๅจๆงๅถๅ
ถไป่ดฆๆท๏ผๆไปฅๆ ๆณๆทปๅ ๆงๅถ่
ใ่ดฆๆทๅช่ฝๆฅๆๆงๅถ่
ๆๆงๅถๅ
ถไป่ดฆๆท๏ผไธ่ฝๅๆถไธค่
ๅ
ผๅคใ",
850
848
"cannotControlAccounts": "ๅ ไธบๆญค่ดฆๆทๆๆงๅถ่
๏ผๆไปฅๆ ๆณๆงๅถๅ
ถไป่ดฆๆทใ่ดฆๆทๅช่ฝๆฅๆๆงๅถ่
ๆๆงๅถๅ
ถไป่ดฆๆท๏ผไธ่ฝๅๆถไธค่
ๅ
ผๅคใ",
851
849
"controlledAccountsDesc": "ๆจๅฏไปฅไปฃ็ๆไฝ็่ดฆๆท",
850
+
"controlledAccountsLocalOnly": "ไป
ๆพ็คบๆญคPDSไธ็่ดฆๆทใๅ
ถไปPDSๅฏ่ฝๅทฒๅ็ฌๆไบๆจๆงๅถ่
่ฎฟ้ฎๆ้",
852
851
"controllerAdded": "ๆงๅถ่
ๅทฒๆทปๅ ",
853
852
"controllerDid": "ๆงๅถ่
DID",
854
853
"controllerRemoved": "ๆงๅถ่
ๅทฒ็งป้ค",
···
861
860
"inactive": "ๆชๆฟๆดป",
862
861
"remove": "็งป้ค",
863
862
"removeConfirm": "็กฎๅฎ่ฆ็งป้คๆญคๆงๅถ่
ๅ๏ผ",
864
-
"viewAuditLog": "ๆฅ็ๅฎก่ฎกๆฅๅฟ",
865
863
"yourAccessLevel": "ๆจ็่ฎฟ้ฎ็บงๅซ",
866
864
"noAuditEntries": "ๆ ๅฎก่ฎก่ฎฐๅฝ",
867
865
"target": "็ฎๆ ",
868
-
"pageInfo": "{start} - {end} / {total}",
869
866
"failedToLoadAudit": "ๅ ่ฝฝๅฎก่ฎกๆฅๅฟๅคฑ่ดฅ"
870
867
},
871
868
"actAs": {
+5
-7
frontend/src/routes/Dashboard.svelte
+5
-7
frontend/src/routes/Dashboard.svelte
···
24
24
import InviteCodesContent from '../components/dashboard/InviteCodesContent.svelte'
25
25
import DidDocumentContent from '../components/dashboard/DidDocumentContent.svelte'
26
26
import AdminContent from '../components/dashboard/AdminContent.svelte'
27
-
import DelegationAuditContent from '../components/dashboard/DelegationAuditContent.svelte'
28
27
29
-
type Section = 'settings' | 'security' | 'sessions' | 'app-passwords' | 'comms' | 'repo' | 'controllers' | 'delegation-audit' | 'invite-codes' | 'did-document' | 'admin'
28
+
type Section = 'settings' | 'security' | 'sessions' | 'app-passwords' | 'comms' | 'repo' | 'controllers' | 'invite-codes' | 'did-document' | 'admin'
30
29
31
30
const auth = $derived(getAuthState())
32
31
let dropdownOpen = $state(false)
···
72
71
'/comms': 'comms',
73
72
'/repo': 'repo',
74
73
'/controllers': 'controllers',
75
-
'/delegation-audit': 'delegation-audit',
74
+
76
75
'/invite-codes': 'invite-codes',
77
76
'/did-document': 'did-document',
78
77
'/admin': 'admin',
···
147
146
'comms': '/comms',
148
147
'repo': '/repo',
149
148
'controllers': '/controllers',
150
-
'delegation-audit': '/delegation-audit',
149
+
151
150
'invite-codes': '/invite-codes',
152
151
'did-document': '/did-document',
153
152
'admin': '/admin',
···
176
175
{ id: 'comms', label: $_('dashboard.navComms'), show: session?.accountKind !== 'migrated' },
177
176
{ id: 'repo', label: $_('dashboard.navRepo'), show: session?.accountKind !== 'migrated' },
178
177
{ id: 'controllers', label: $_('dashboard.navDelegation'), show: session?.accountKind !== 'migrated' },
179
-
{ id: 'delegation-audit', label: $_('dashboard.navDelegationAudit'), show: session?.accountKind !== 'migrated' },
178
+
180
179
{ id: 'invite-codes', label: $_('dashboard.navInviteCodes'), show: inviteCodesEnabled && (session?.isAdmin ?? false) && session?.accountKind !== 'migrated' },
181
180
{ id: 'did-document', label: $_('dashboard.navDidDocument'), show: isPdsHostedDidWeb || session?.accountKind === 'migrated', highlight: session?.accountKind === 'migrated' ? 'migrated' : 'did-web' },
182
181
{ id: 'admin', label: $_('dashboard.navAdmin'), show: session?.isAdmin ?? false, highlight: 'admin' },
···
303
302
<RepoContent {session} />
304
303
{:else if currentSection === 'controllers'}
305
304
<ControllersContent {session} />
306
-
{:else if currentSection === 'delegation-audit'}
307
-
<DelegationAuditContent {session} />
305
+
308
306
{:else if currentSection === 'invite-codes'}
309
307
<InviteCodesContent {session} />
310
308
{:else if currentSection === 'did-document'}
History
1 round
0 comments
oyster.cafe
submitted
#0
1 commit
expand
collapse
chore: inline delegation audit page
expand 0 comments
pull request successfully merged