@svadmin/ui 0.34.8 → 0.34.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/component-registry.svelte.js +1 -0
- package/dist/components/AICommandBar.svelte +3 -3
- package/dist/components/AboutSettings.svelte +2 -2
- package/dist/components/AdminApp.svelte +2 -3
- package/dist/components/AnomalyBadge.svelte +1 -1
- package/dist/components/AppearanceSettings.svelte +5 -6
- package/dist/components/AuditLogDrawer.svelte +1 -1
- package/dist/components/AuditLogViewer.svelte +1 -1
- package/dist/components/AutoForm.svelte +5 -4
- package/dist/components/AutoTable.svelte +126 -57
- package/dist/components/Breadcrumbs.svelte +1 -0
- package/dist/components/ChatDialog.svelte +5 -4
- package/dist/components/ComboboxField.svelte +3 -3
- package/dist/components/CommandPalette.svelte +3 -3
- package/dist/components/CopilotPanel.svelte +2 -2
- package/dist/components/CreatePage.svelte +0 -1
- package/dist/components/DevTools.svelte +4 -3
- package/dist/components/DraggableHeader.svelte +2 -1
- package/dist/components/EditPage.svelte +1 -1
- package/dist/components/EmptyState.svelte +1 -1
- package/dist/components/FieldRenderer.svelte +7 -6
- package/dist/components/Header.svelte +1 -0
- package/dist/components/InferencerPanel.svelte +4 -3
- package/dist/components/InfiniteList.svelte +3 -4
- package/dist/components/KeyboardShortcuts.svelte +3 -3
- package/dist/components/Layout.svelte +5 -5
- package/dist/components/MarkdownRenderer.svelte +3 -5
- package/dist/components/ModalForm.svelte +1 -3
- package/dist/components/PageSkeleton.svelte +4 -4
- package/dist/components/PasswordInput.svelte +1 -2
- package/dist/components/PermissionMatrix.svelte +5 -5
- package/dist/components/ProfilePage.svelte +1 -1
- package/dist/components/RolesSettings.svelte +1 -2
- package/dist/components/SettingsPage.svelte +4 -5
- package/dist/components/ShowPage.svelte +4 -6
- package/dist/components/Sidebar.svelte +12 -11
- package/dist/components/SidebarItem.svelte +1 -2
- package/dist/components/StepsForm.svelte +1 -1
- package/dist/components/TaskList.svelte +1 -1
- package/dist/components/TaskQueueDrawer.svelte +2 -1
- package/dist/components/TenantSwitcher.svelte +1 -2
- package/dist/components/Toast.svelte +1 -0
- package/dist/components/TooltipButton.svelte +1 -0
- package/dist/components/VirtualTable.svelte +1 -1
- package/dist/components/VoiceInput.svelte +1 -0
- package/dist/components/buttons/ImportButton.svelte +1 -1
- package/dist/components/buttons/ListButton.svelte +1 -1
- package/dist/components/charts/BarChart.svelte +1 -1
- package/dist/components/charts/LineChart.svelte +3 -3
- package/dist/components/charts/PieChart.svelte +2 -2
- package/dist/components/fieldComponentMap.js +2 -1
- package/dist/components/fields/ArrayField.svelte +1 -1
- package/dist/components/fields/MarkdownField.svelte +1 -0
- package/dist/components/fields/MultiSelectField.svelte +1 -1
- package/dist/components/fields/TagField.svelte +1 -1
- package/dist/components/ui/alert/index.js +1 -0
- package/dist/components/ui/badge/index.js +1 -0
- package/dist/components/ui/button/button.svelte +1 -0
- package/dist/components/ui/button/index.js +1 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +1 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.svelte +1 -1
- package/dist/components/ui/sheet/sheet.svelte +1 -0
- package/dist/components/ui/tabs/tabs.svelte +1 -0
- package/package.json +13 -13
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
3
|
/**
|
|
3
4
|
* DraggableHeader — Wraps table header cells with HTML5 Drag and Drop
|
|
4
5
|
* to enable column reorder. Persists order to localStorage.
|
|
@@ -99,7 +100,7 @@
|
|
|
99
100
|
</script>
|
|
100
101
|
|
|
101
102
|
<tr class="hover:bg-muted/50 data-[state=selected]:bg-muted transition-colors bg-muted/50">
|
|
102
|
-
{#each columns as column, index}
|
|
103
|
+
{#each columns as column, index (index)}
|
|
103
104
|
{@render header(column, index, {
|
|
104
105
|
draggable: true,
|
|
105
106
|
ondragstart: (e: DragEvent) => handleDragStart(e, index),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
+
/* eslint-disable no-import-assign */
|
|
2
3
|
export type { FieldDefinition } from '@svadmin/core';
|
|
3
4
|
</script>
|
|
4
5
|
|
|
@@ -240,7 +241,7 @@
|
|
|
240
241
|
disabled={disabled}
|
|
241
242
|
>
|
|
242
243
|
<option value="">{t('field.selectPlaceholder')}</option>
|
|
243
|
-
{#each field.options ?? [] as opt}
|
|
244
|
+
{#each field.options ?? [] as opt, _i (_i)}
|
|
244
245
|
<option value={opt.value}>{opt.label}</option>
|
|
245
246
|
{/each}
|
|
246
247
|
</select>
|
|
@@ -253,7 +254,7 @@
|
|
|
253
254
|
{disabled}
|
|
254
255
|
placeholder={t('field.selectPlaceholder')}
|
|
255
256
|
>
|
|
256
|
-
{#each field.options ?? [] as opt}
|
|
257
|
+
{#each field.options ?? [] as opt, _i (_i)}
|
|
257
258
|
<option value={opt.value}>{opt.label}</option>
|
|
258
259
|
{/each}
|
|
259
260
|
</Select>
|
|
@@ -265,7 +266,7 @@
|
|
|
265
266
|
role="group"
|
|
266
267
|
aria-labelledby="label-{field.key}"
|
|
267
268
|
>
|
|
268
|
-
{#each field.options ?? [] as opt}
|
|
269
|
+
{#each field.options ?? [] as opt, _i (_i)}
|
|
269
270
|
<label class="flex items-center gap-2 text-sm cursor-pointer hover:bg-muted/50 rounded px-1 py-0.5 transition-colors">
|
|
270
271
|
<Checkbox
|
|
271
272
|
id={`${field.key}-${opt.value}`}
|
|
@@ -282,7 +283,7 @@
|
|
|
282
283
|
</div>
|
|
283
284
|
{#if multiVal.length > 0}
|
|
284
285
|
<div class="flex flex-wrap gap-1 mt-1">
|
|
285
|
-
{#each multiVal as v}
|
|
286
|
+
{#each multiVal as v, _i (_i)}
|
|
286
287
|
{@const label = field.options?.find((o: { label: string; value: string | number }) => o.value === v)?.label ?? String(v)}
|
|
287
288
|
<Badge variant="secondary" class="gap-1">
|
|
288
289
|
{label}
|
|
@@ -306,7 +307,7 @@
|
|
|
306
307
|
{:else if field.type === 'tags'}
|
|
307
308
|
<div class="space-y-2">
|
|
308
309
|
<div class="flex flex-wrap gap-1.5">
|
|
309
|
-
{#each tagsVal as tag, i}
|
|
310
|
+
{#each tagsVal as tag, i (i)}
|
|
310
311
|
<Badge variant="secondary" class="gap-1">
|
|
311
312
|
{tag}
|
|
312
313
|
<button
|
|
@@ -339,7 +340,7 @@
|
|
|
339
340
|
|
|
340
341
|
{:else if field.type === 'images'}
|
|
341
342
|
<div class="space-y-2">
|
|
342
|
-
{#each imagesVal as url, i}
|
|
343
|
+
{#each imagesVal as url, i (i)}
|
|
343
344
|
<div class="flex items-center gap-2">
|
|
344
345
|
<Input
|
|
345
346
|
id={i === 0 ? field.key : undefined}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
|
|
2
3
|
import { getDataProvider, getResources, inferResource } from '@svadmin/core';
|
|
3
4
|
import type { InferResult } from '@svadmin/core';
|
|
4
5
|
import { Button } from './ui/button/index.js';
|
|
@@ -99,7 +100,7 @@
|
|
|
99
100
|
bind:value={selectedResource}
|
|
100
101
|
placeholder="— Select a resource —"
|
|
101
102
|
>
|
|
102
|
-
{#each resources as res}
|
|
103
|
+
{#each resources as res, _i (_i)}
|
|
103
104
|
<option value={res.name}>{res.label} ({res.name})</option>
|
|
104
105
|
{/each}
|
|
105
106
|
</Select>
|
|
@@ -131,7 +132,7 @@
|
|
|
131
132
|
|
|
132
133
|
{#if loading}
|
|
133
134
|
<div class="space-y-2">
|
|
134
|
-
{#each Array(4) as _}
|
|
135
|
+
{#each Array(4) as _, _i (_i)}
|
|
135
136
|
<Skeleton class="h-8 w-full" />
|
|
136
137
|
{/each}
|
|
137
138
|
</div>
|
|
@@ -150,7 +151,7 @@
|
|
|
150
151
|
</Table.Row>
|
|
151
152
|
</Table.Header>
|
|
152
153
|
<Table.Body>
|
|
153
|
-
{#each inferResult.fields as field}
|
|
154
|
+
{#each inferResult.fields as field, _i (_i)}
|
|
154
155
|
<Table.Row>
|
|
155
156
|
<Table.Cell class="font-mono text-xs">{field.key}</Table.Cell>
|
|
156
157
|
<Table.Cell>
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { fade
|
|
2
|
+
import { fade } from 'svelte/transition';
|
|
3
3
|
import { useInfiniteList } from '@svadmin/core';
|
|
4
4
|
import type { BaseRecord, Sort, Filter } from '@svadmin/core';
|
|
5
5
|
import type { Snippet } from 'svelte';
|
|
6
6
|
import { t } from '@svadmin/core/i18n';
|
|
7
7
|
import { Skeleton } from './ui/skeleton/index.js';
|
|
8
|
-
import { Button } from './ui/button/index.js';
|
|
9
8
|
import { Loader2 } from '@lucide/svelte';
|
|
10
9
|
import { intersect } from '../actions.js';
|
|
11
10
|
|
|
@@ -59,7 +58,7 @@
|
|
|
59
58
|
{@render loadingSkeleton()}
|
|
60
59
|
{:else}
|
|
61
60
|
<div class="space-y-3">
|
|
62
|
-
{#each Array(5) as _}
|
|
61
|
+
{#each Array(5) as _, _i (_i)}
|
|
63
62
|
<Skeleton class="h-16 w-full rounded-lg" />
|
|
64
63
|
{/each}
|
|
65
64
|
</div>
|
|
@@ -71,7 +70,7 @@
|
|
|
71
70
|
<p class="text-center text-sm text-muted-foreground py-8">{t('empty.title')}</p>
|
|
72
71
|
{/if}
|
|
73
72
|
{:else}
|
|
74
|
-
{#each allItems as item, index}
|
|
73
|
+
{#each allItems as item, index (index)}
|
|
75
74
|
<div in:fade={{ duration: 200, delay: index < 20 ? index * 30 : 0 }}>
|
|
76
75
|
{@render children({ item, index })}
|
|
77
76
|
</div>
|
|
@@ -39,15 +39,15 @@
|
|
|
39
39
|
<Dialog.Title>{t('common.keyboardShortcuts') || 'Keyboard Shortcuts'}</Dialog.Title>
|
|
40
40
|
</Dialog.Header>
|
|
41
41
|
<div class="space-y-4 py-2">
|
|
42
|
-
{#each shortcuts as group}
|
|
42
|
+
{#each shortcuts as group, _i (_i)}
|
|
43
43
|
<div>
|
|
44
44
|
<h4 class="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-2">{group.group}</h4>
|
|
45
45
|
<div class="space-y-1.5">
|
|
46
|
-
{#each group.items as shortcut}
|
|
46
|
+
{#each group.items as shortcut, _i (_i)}
|
|
47
47
|
<div class="flex items-center justify-between py-1">
|
|
48
48
|
<span class="text-sm text-foreground">{shortcut.label}</span>
|
|
49
49
|
<div class="flex items-center gap-1">
|
|
50
|
-
{#each shortcut.keys as key}
|
|
50
|
+
{#each shortcut.keys as key, _i (_i)}
|
|
51
51
|
<kbd class="inline-flex h-5 min-w-5 items-center justify-center rounded border bg-muted px-1.5 text-[10px] font-medium font-mono text-muted-foreground">{key}</kbd>
|
|
52
52
|
{/each}
|
|
53
53
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/* eslint-disable svelte/no-useless-children-snippet */
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
import { fade, fly } from 'svelte/transition';
|
|
4
5
|
import Sidebar from './Sidebar.svelte';
|
|
@@ -13,7 +14,6 @@
|
|
|
13
14
|
import { navigate } from '@svadmin/core/router';
|
|
14
15
|
import { getPath } from '../router-state.svelte.js';
|
|
15
16
|
import { Skeleton } from './ui/skeleton/index.js';
|
|
16
|
-
import { Button } from './ui/button/index.js';
|
|
17
17
|
import * as Sheet from './ui/sheet/index.js';
|
|
18
18
|
import { Menu } from '@lucide/svelte';
|
|
19
19
|
import { getComponentRegistry } from '../component-registry.svelte.js';
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
|
|
25
25
|
let { children, title = 'Admin', menu, siteUrl, routeMode = 'auto' }: { children: Snippet; title?: string; menu?: MenuItem[]; siteUrl?: string; routeMode?: 'hash' | 'path' | 'auto' } = $props();
|
|
26
26
|
|
|
27
|
-
let
|
|
27
|
+
let _hasAuth = true;
|
|
28
28
|
let auth: ReturnType<typeof getAuthProvider> | null = null;
|
|
29
29
|
try {
|
|
30
30
|
auth = getAuthProvider();
|
|
31
31
|
} catch {
|
|
32
|
-
|
|
32
|
+
_hasAuth = false;
|
|
33
33
|
}
|
|
34
34
|
let loading = $state(true);
|
|
35
35
|
let identity = $state<Identity | null>(null);
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
<div class="hidden md:block w-64 bg-sidebar/80 p-4 space-y-4">
|
|
109
109
|
<Skeleton class="h-8 w-32" />
|
|
110
110
|
<div class="space-y-2 mt-6">
|
|
111
|
-
{#each Array(5) as _}
|
|
111
|
+
{#each Array(5) as _, _i (_i)}
|
|
112
112
|
<Skeleton class="h-9 w-full rounded-lg" />
|
|
113
113
|
{/each}
|
|
114
114
|
</div>
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
<div class="flex-1 p-8 space-y-6">
|
|
117
117
|
<Skeleton class="h-8 w-48" />
|
|
118
118
|
<div class="space-y-3">
|
|
119
|
-
{#each Array(4) as _}
|
|
119
|
+
{#each Array(4) as _, _i (_i)}
|
|
120
120
|
<Skeleton class="h-12 w-full" />
|
|
121
121
|
{/each}
|
|
122
122
|
</div>
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/* eslint-disable svelte/no-at-html-tags */
|
|
2
3
|
import * as markedPkg from "marked";
|
|
3
4
|
import * as markedHighlightPkg from "marked-highlight";
|
|
4
5
|
import * as hljsPkg from "highlight.js";
|
|
5
6
|
import * as DOMPurifyPkg from "isomorphic-dompurify";
|
|
6
|
-
import { Check, Copy } from '@lucide/svelte';
|
|
7
|
-
import { Button } from "./ui/button/index.js";
|
|
8
7
|
|
|
9
8
|
const Marked = markedPkg.Marked;
|
|
10
9
|
const markedHighlight = markedHighlightPkg.markedHighlight;
|
|
@@ -13,7 +12,6 @@
|
|
|
13
12
|
|
|
14
13
|
// Only safely import css if hljs exists
|
|
15
14
|
if (hljsModule && Object.keys(hljsModule).length > 0) {
|
|
16
|
-
// @ts-ignore
|
|
17
15
|
import("highlight.js/styles/github-dark.css").catch(() => {});
|
|
18
16
|
}
|
|
19
17
|
|
|
@@ -44,7 +42,7 @@
|
|
|
44
42
|
// Render HTML safely
|
|
45
43
|
const html = $derived(
|
|
46
44
|
hasMarkdownDeps
|
|
47
|
-
? DOMPurify.sanitize(markedObj
|
|
45
|
+
? DOMPurify.sanitize(markedObj?.parse(content || "") as string)
|
|
48
46
|
: `<div style="white-space: pre-wrap">${String(content || "").replace(/</g, "<").replace(/>/g, ">")}</div>`
|
|
49
47
|
);
|
|
50
48
|
|
|
@@ -75,7 +73,7 @@
|
|
|
75
73
|
// We do this via action instead of raw string manipulation for safety
|
|
76
74
|
function enhanceCodeBlocks(node: HTMLElement, _: string) {
|
|
77
75
|
return {
|
|
78
|
-
update(
|
|
76
|
+
update(_newHtml: string) {
|
|
79
77
|
// Find all pre > code blocks that don't have wrappers yet
|
|
80
78
|
const pres = node.querySelectorAll("pre:not(.enhanced)");
|
|
81
79
|
pres.forEach((pre) => {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import
|
|
3
|
-
import type { FieldDefinition } from '@svadmin/core';
|
|
4
|
-
import { getResource, useForm } from '@svadmin/core';
|
|
2
|
+
import { getResource } from '@svadmin/core';
|
|
5
3
|
import { t } from '@svadmin/core/i18n';
|
|
6
4
|
import * as Dialog from './ui/dialog/index.js';
|
|
7
5
|
import AutoForm from './AutoForm.svelte';
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
{#if type === 'list'}
|
|
28
28
|
<!-- Table Header -->
|
|
29
29
|
<div class="flex gap-4 border-b pb-4">
|
|
30
|
-
{#each Array(4) as _}
|
|
30
|
+
{#each Array(4) as _, _i (_i)}
|
|
31
31
|
<Skeleton class="h-6 flex-1" />
|
|
32
32
|
{/each}
|
|
33
33
|
</div>
|
|
34
34
|
<!-- Table Rows -->
|
|
35
35
|
<div class="space-y-4">
|
|
36
|
-
{#each Array(rows) as _}
|
|
36
|
+
{#each Array(rows) as _, _i (_i)}
|
|
37
37
|
<div class="flex gap-4">
|
|
38
38
|
<Skeleton class="h-6 flex-1" />
|
|
39
39
|
<Skeleton class="h-6 flex-1" />
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
{:else if type === 'show'}
|
|
46
46
|
<!-- Key-Value pairs -->
|
|
47
47
|
<div class="space-y-6">
|
|
48
|
-
{#each Array(rows) as _}
|
|
48
|
+
{#each Array(rows) as _, _i (_i)}
|
|
49
49
|
<div class="space-y-2">
|
|
50
50
|
<Skeleton class="h-4 w-[100px]" />
|
|
51
51
|
<Skeleton class="h-6 w-[250px]" />
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
{:else if type === 'form'}
|
|
56
56
|
<!-- Form Inputs -->
|
|
57
57
|
<div class="space-y-6">
|
|
58
|
-
{#each Array(rows) as _}
|
|
58
|
+
{#each Array(rows) as _, _i (_i)}
|
|
59
59
|
<div class="space-y-2">
|
|
60
60
|
<Skeleton class="h-4 w-[100px]" />
|
|
61
61
|
<Skeleton class="h-10 w-full max-w-md" />
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
3
|
import { Input } from './ui/input/index.js';
|
|
3
|
-
import { Button } from './ui/button/index.js';
|
|
4
4
|
import TooltipButton from './TooltipButton.svelte';
|
|
5
5
|
import { Label } from './ui/label/index.js';
|
|
6
|
-
import { Progress } from './ui/progress/index.js';
|
|
7
6
|
import { Lock, Eye, EyeOff } from '@lucide/svelte';
|
|
8
7
|
import { t } from '@svadmin/core/i18n';
|
|
9
8
|
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
<div class="w-full md:w-64 flex-shrink-0 bg-card rounded-xl shadow-sm border border-border p-4 flex flex-col">
|
|
81
81
|
<h2 class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-4 px-2">{t('permissions.roles') || 'Roles'}</h2>
|
|
82
82
|
<div class="space-y-1 flex-1 overflow-y-auto">
|
|
83
|
-
{#each roles as role}
|
|
83
|
+
{#each roles as role, _i (_i)}
|
|
84
84
|
<button
|
|
85
85
|
onclick={() => selectedRole = role.code}
|
|
86
86
|
class="w-full text-left px-3 py-2.5 rounded-lg text-sm transition-all duration-200 {selectedRole === role.code ? 'bg-primary/10 text-primary font-medium shadow-sm ring-1 ring-primary/20' : 'text-foreground hover:bg-muted'}"
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
<thead class="bg-muted/50">
|
|
134
134
|
<tr>
|
|
135
135
|
<th scope="col" class="py-3 pr-3 pl-4 text-left text-sm font-semibold text-foreground w-48">{t('permissions.resources') || 'Resources'}</th>
|
|
136
|
-
{#each actions as action}
|
|
136
|
+
{#each actions as action, _i (_i)}
|
|
137
137
|
<th scope="col" class="px-2 py-3 text-center text-sm font-semibold text-foreground">
|
|
138
138
|
<div class="flex flex-col items-center">
|
|
139
139
|
<span>{action.name}</span>
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
<td colspan={actions.length + 1} class="py-10 text-center text-muted-foreground">{t('permissions.noResources') || 'No resources available'}</td>
|
|
150
150
|
</tr>
|
|
151
151
|
{/if}
|
|
152
|
-
{#each resources as resource, i}
|
|
152
|
+
{#each resources as resource, i (i)}
|
|
153
153
|
<!-- Section Grouping Header -->
|
|
154
154
|
{#if resource.section && (i === 0 || resource.section !== resources[i-1].section)}
|
|
155
155
|
<tr class="bg-primary/5">
|
|
@@ -164,14 +164,14 @@
|
|
|
164
164
|
</td>
|
|
165
165
|
|
|
166
166
|
<!-- Action Checkboxes -->
|
|
167
|
-
{#each actions as action}
|
|
167
|
+
{#each actions as action, _i (_i)}
|
|
168
168
|
{@const granted = selectedRole ? isGranted(selectedRole, resource.code, action.code) : false}
|
|
169
169
|
<td class="whitespace-nowrap px-3 py-3 text-center">
|
|
170
170
|
<div class="flex items-center justify-center">
|
|
171
171
|
<button
|
|
172
172
|
disabled={loading || !selectedRole}
|
|
173
173
|
aria-label="Toggle {action.name}"
|
|
174
|
-
onclick={() => onToggle(selectedRole
|
|
174
|
+
onclick={() => onToggle(selectedRole as string, resource.code, action.code, !granted)}
|
|
175
175
|
class="w-6 h-6 rounded border flex items-center justify-center transition-all focus:outline-none focus:ring-2 focus:ring-primary/50 {granted ? 'bg-primary border-primary text-primary-foreground' : 'border-input bg-background hover:bg-accent'} {loading || !selectedRole ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}"
|
|
176
176
|
>
|
|
177
177
|
<svg class="w-3.5 h-3.5 {granted ? 'block' : 'hidden'}" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
|
|
54
54
|
// Avatar upload
|
|
55
55
|
let avatarUploading = $state(false);
|
|
56
|
-
let fileInput = $state<HTMLInputElement>(null!);
|
|
56
|
+
let fileInput = $state<HTMLInputElement>(null!); // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
57
57
|
|
|
58
58
|
async function handleAvatarChange(e: Event) {
|
|
59
59
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { t } from '@svadmin/core/i18n';
|
|
3
3
|
import { getAuthProvider, getResources } from '@svadmin/core';
|
|
4
|
-
import type { Role } from '@svadmin/core';
|
|
5
4
|
import PermissionMatrix from './PermissionMatrix.svelte';
|
|
6
5
|
import type { RoleInfo, ResourceInfo, ActionInfo } from '../types.js';
|
|
7
6
|
import { toast } from '@svadmin/core/toast';
|
|
@@ -117,7 +116,7 @@
|
|
|
117
116
|
} catch (e) {
|
|
118
117
|
toast.error((e as Error).message);
|
|
119
118
|
// Rollback on fail
|
|
120
|
-
delete permissionsCache[roleCode];
|
|
119
|
+
delete permissionsCache[roleCode]; // eslint-disable-line @typescript-eslint/no-dynamic-delete
|
|
121
120
|
loadPermissionsForRole(roleCode);
|
|
122
121
|
} finally {
|
|
123
122
|
saving = false;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { t } from '@svadmin/core/i18n';
|
|
3
3
|
import { navigate } from '@svadmin/core/router';
|
|
4
|
-
import { currentPath } from '@svadmin/core/router';
|
|
5
4
|
import { User, Palette, Info, Shield, FileSearch } from '@lucide/svelte';
|
|
6
5
|
import ProfilePage from './ProfilePage.svelte';
|
|
7
6
|
import AppearanceSettings from './AppearanceSettings.svelte';
|
|
@@ -56,8 +55,8 @@
|
|
|
56
55
|
<nav class="w-full lg:w-56 shrink-0 bg-muted/10 shadow-[inset_0_-1px_0_rgba(0,0,0,0.05)] lg:shadow-[inset_-1px_0_0_rgba(0,0,0,0.05)] dark:shadow-[inset_-1px_0_0_rgba(255,255,255,0.05)]">
|
|
57
56
|
<!-- Mobile: horizontal scroll tabs -->
|
|
58
57
|
<div class="flex lg:hidden overflow-x-auto px-4 py-2 gap-1">
|
|
59
|
-
{#each sections as section}
|
|
60
|
-
{#each section.items as item}
|
|
58
|
+
{#each sections as section, _i (_i)}
|
|
59
|
+
{#each section.items as item, _i (_i)}
|
|
61
60
|
{@const active = activeKey === item.key}
|
|
62
61
|
<button
|
|
63
62
|
class="flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md whitespace-nowrap transition-colors
|
|
@@ -77,13 +76,13 @@
|
|
|
77
76
|
<h2 class="text-lg font-semibold text-foreground">{t('settings.title')}</h2>
|
|
78
77
|
<p class="text-xs text-muted-foreground mt-1">{t('settings.settingsDescription')}</p>
|
|
79
78
|
</div>
|
|
80
|
-
{#each sections as section}
|
|
79
|
+
{#each sections as section, _i (_i)}
|
|
81
80
|
<div>
|
|
82
81
|
<h3 class="px-3 mb-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60">
|
|
83
82
|
{t(section.group)}
|
|
84
83
|
</h3>
|
|
85
84
|
<div class="space-y-0.5">
|
|
86
|
-
{#each section.items as item}
|
|
85
|
+
{#each section.items as item, _i (_i)}
|
|
87
86
|
{@const active = activeKey === item.key}
|
|
88
87
|
<button
|
|
89
88
|
class="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm transition-all
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useShow, getResource } from '@svadmin/core';
|
|
3
|
-
import type { FieldDefinition } from '@svadmin/core';
|
|
4
3
|
import { navigate } from '@svadmin/core/router';
|
|
5
|
-
import { t
|
|
6
|
-
import { Button } from './ui/button/index.js';
|
|
4
|
+
import { t } from '@svadmin/core/i18n';
|
|
7
5
|
import * as Card from './ui/card/index.js';
|
|
8
6
|
import { Skeleton } from './ui/skeleton/index.js';
|
|
9
7
|
import PageHeader from './PageHeader.svelte';
|
|
@@ -42,7 +40,7 @@
|
|
|
42
40
|
{#if query.isLoading}
|
|
43
41
|
<Card.Root class="overflow-hidden border-border/40 shadow-sm">
|
|
44
42
|
<Card.Content class="p-0">
|
|
45
|
-
{#each showFields.slice(0, 6) as _, i}
|
|
43
|
+
{#each showFields.slice(0, 6) as _, i (i)}
|
|
46
44
|
<div class="flex flex-col sm:flex-row px-4 sm:px-6 py-3 sm:py-4 {i % 2 === 1 ? 'bg-muted/20' : ''}">
|
|
47
45
|
<Skeleton class="h-4 w-1/2 sm:w-1/4" />
|
|
48
46
|
<Skeleton class="h-4 w-3/4 sm:w-2/5 mt-1 sm:mt-0 sm:ml-auto" />
|
|
@@ -53,8 +51,8 @@
|
|
|
53
51
|
{:else if query.data?.data}
|
|
54
52
|
<Card.Root class="overflow-hidden border-border/40 shadow-sm">
|
|
55
53
|
<Card.Content class="p-0">
|
|
56
|
-
{#each showFields as field, i}
|
|
57
|
-
{@const value = (query.data
|
|
54
|
+
{#each showFields as field, i (i)}
|
|
55
|
+
{@const value = ((query.data as { data: Record<string, unknown> }).data as Record<string, unknown>)[field.key]}
|
|
58
56
|
{@const DisplayComponent = getDisplayComponent(field.type)}
|
|
59
57
|
<div class="flex flex-col sm:flex-row px-4 sm:px-6 py-3 sm:py-4 {i % 2 === 1 ? 'bg-muted/20' : ''}">
|
|
60
58
|
<div class="sm:w-1/3 text-xs sm:text-sm font-medium text-muted-foreground mb-1 sm:mb-0">{field.label}</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/* eslint-disable svelte/prefer-svelte-reactivity */
|
|
2
3
|
import { getResources, canAccessAsync } from '@svadmin/core';
|
|
3
4
|
import type { Identity, MenuItem } from '@svadmin/core';
|
|
4
5
|
import { navigate } from '@svadmin/core/router';
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
: routeMode
|
|
38
39
|
);
|
|
39
40
|
|
|
40
|
-
const
|
|
41
|
+
const _resources = getResources();
|
|
41
42
|
|
|
42
43
|
const iconMap: Record<string, typeof LayoutDashboard> = {
|
|
43
44
|
'dashboard': LayoutDashboard,
|
|
@@ -80,7 +81,7 @@
|
|
|
80
81
|
let navItems = $state.raw<NavItem[]>([]);
|
|
81
82
|
|
|
82
83
|
$effect(() => {
|
|
83
|
-
const
|
|
84
|
+
const _localeVal = getLocale();
|
|
84
85
|
const currentResources = getResources();
|
|
85
86
|
let cancelled = false;
|
|
86
87
|
|
|
@@ -132,7 +133,7 @@
|
|
|
132
133
|
|
|
133
134
|
/** Prefetch resource data on hover for instant navigation */
|
|
134
135
|
const prefetchedResources = new Set<string>();
|
|
135
|
-
function
|
|
136
|
+
function _prefetchResource(resourceName: string) {
|
|
136
137
|
if (resourceName === '/' || prefetchedResources.has(resourceName)) return;
|
|
137
138
|
prefetchedResources.add(resourceName);
|
|
138
139
|
// Trigger a background fetch by navigating the browser cache
|
|
@@ -148,9 +149,9 @@
|
|
|
148
149
|
const key = item.group ?? null;
|
|
149
150
|
if (!groupMap.has(key)) {
|
|
150
151
|
groupMap.set(key, []);
|
|
151
|
-
groups.push({ name: key, items: groupMap.get(key)
|
|
152
|
+
groups.push({ name: key, items: groupMap.get(key) ?? [] });
|
|
152
153
|
}
|
|
153
|
-
groupMap.get(key)
|
|
154
|
+
(groupMap.get(key) ?? []).push(item);
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
return groups;
|
|
@@ -233,17 +234,17 @@
|
|
|
233
234
|
<nav aria-label="Main menu" class="py-4 px-3 space-y-1">
|
|
234
235
|
{#if menu && menu.length > 0}
|
|
235
236
|
<!-- Custom multi-level menu -->
|
|
236
|
-
{#each menu as item}
|
|
237
|
+
{#each menu as item, _i (_i)}
|
|
237
238
|
<SidebarItem {item} currentPath={path} {collapsed} depth={0} />
|
|
238
239
|
{/each}
|
|
239
240
|
{:else}
|
|
240
241
|
<!-- Auto-generated menu from resources (fallback) -->
|
|
241
|
-
{#each navGroups as group}
|
|
242
|
+
{#each navGroups as group, _i (_i)}
|
|
242
243
|
{#if group.name && !collapsed}
|
|
243
244
|
<!-- Grouped section with Collapsible -->
|
|
244
245
|
<Collapsible.Root open={openGroups.has(group.name)} onOpenChange={(isOpen) => {
|
|
245
246
|
const next = new Set(openGroups);
|
|
246
|
-
if (isOpen) next.add(group.name
|
|
247
|
+
if (isOpen) next.add(group.name as string); else next.delete(group.name as string);
|
|
247
248
|
openGroups = next;
|
|
248
249
|
}}>
|
|
249
250
|
<Collapsible.Trigger
|
|
@@ -254,7 +255,7 @@
|
|
|
254
255
|
</Collapsible.Trigger>
|
|
255
256
|
<Collapsible.Content>
|
|
256
257
|
<div class="mt-1 space-y-1">
|
|
257
|
-
{#each group.items as item}
|
|
258
|
+
{#each group.items as item, _i (_i)}
|
|
258
259
|
{@const active = isActive(item.path)}
|
|
259
260
|
<a
|
|
260
261
|
href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
|
|
@@ -273,7 +274,7 @@
|
|
|
273
274
|
</Collapsible.Root>
|
|
274
275
|
{:else}
|
|
275
276
|
<!-- Ungrouped items (flat) -->
|
|
276
|
-
{#each group.items as item}
|
|
277
|
+
{#each group.items as item, _i (_i)}
|
|
277
278
|
{@const active = isActive(item.path)}
|
|
278
279
|
{#if collapsed}
|
|
279
280
|
<Tooltip.Root>
|
|
@@ -336,7 +337,7 @@
|
|
|
336
337
|
</Button>
|
|
337
338
|
{#if colorPickerOpen}
|
|
338
339
|
<div class="absolute bottom-full left-0 mb-1 z-50 w-40 rounded-lg border bg-popover p-1 text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95">
|
|
339
|
-
{#each getColorThemes() as ct}
|
|
340
|
+
{#each getColorThemes() as ct, _i (_i)}
|
|
340
341
|
<button
|
|
341
342
|
class="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground transition-colors"
|
|
342
343
|
onclick={() => { setColorTheme(ct.id as typeof ct.id & import('@svadmin/core').ColorTheme); colorPickerOpen = false; }}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { MenuItem } from '@svadmin/core';
|
|
3
|
-
import { t } from '@svadmin/core/i18n';
|
|
4
3
|
import * as Collapsible from './ui/collapsible/index.js';
|
|
5
4
|
import * as Tooltip from './ui/tooltip/index.js';
|
|
6
5
|
import SidebarItem from './SidebarItem.svelte';
|
|
@@ -84,7 +83,7 @@
|
|
|
84
83
|
</Collapsible.Trigger>
|
|
85
84
|
<Collapsible.Content>
|
|
86
85
|
<div class="mt-0.5 space-y-0.5" style="padding-left: {depth > 0 ? 0 : 0}px">
|
|
87
|
-
{#each item.children as child}
|
|
86
|
+
{#each item.children as child, _i (_i)}
|
|
88
87
|
<SidebarItem item={child} {currentPath} {collapsed} depth={depth + 1} />
|
|
89
88
|
{/each}
|
|
90
89
|
</div>
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
</Table.Row>
|
|
111
111
|
</Table.Header>
|
|
112
112
|
<Table.Body>
|
|
113
|
-
{#each resolvedTasks as task}
|
|
113
|
+
{#each resolvedTasks as task, _i (_i)}
|
|
114
114
|
<Table.Row class="cursor-pointer hover:bg-muted/40" onclick={() => onSelect?.(task)}>
|
|
115
115
|
<Table.Cell class="min-w-[240px]">
|
|
116
116
|
<div class="space-y-1">
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
initialTab?: TaskTab;
|
|
37
37
|
} = $props();
|
|
38
38
|
|
|
39
|
+
// eslint-disable-next-line svelte/prefer-writable-derived
|
|
39
40
|
let activeTab = $state<TaskTab>('tasks');
|
|
40
41
|
let selectedTaskId = $state<string | null>(null);
|
|
41
42
|
let searchQuery = $state('');
|
|
@@ -283,7 +284,7 @@
|
|
|
283
284
|
</div>
|
|
284
285
|
<Select bind:value={statusFilter} aria-label={t('task.statusFilterLabel')}>
|
|
285
286
|
<option value="all">{t('task.statusAll')}</option>
|
|
286
|
-
{#each availableStatuses as status}
|
|
287
|
+
{#each availableStatuses as status, _i (_i)}
|
|
287
288
|
<option value={status}>{status}</option>
|
|
288
289
|
{/each}
|
|
289
290
|
</Select>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import { ChevronsUpDown, Building2, Check } from '@lucide/svelte';
|
|
4
|
-
import { Button } from './ui/button/index.js';
|
|
5
4
|
import * as DropdownMenu from './ui/dropdown-menu/index.js';
|
|
6
5
|
import type { Tenant } from '../types.js';
|
|
7
6
|
|
|
@@ -61,7 +60,7 @@
|
|
|
61
60
|
<DropdownMenu.Separator />
|
|
62
61
|
{/if}
|
|
63
62
|
<div class="px-2 py-1.5 text-xs text-muted-foreground">Workspaces</div>
|
|
64
|
-
{#each tenants as tenant}
|
|
63
|
+
{#each tenants as tenant, _i (_i)}
|
|
65
64
|
<DropdownMenu.Item
|
|
66
65
|
class="flex items-center gap-3 cursor-pointer"
|
|
67
66
|
onclick={() => selectTenant(tenant.id)}
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
let scrollTop = $state(0);
|
|
38
38
|
let containerRef = $state<HTMLDivElement | null>(null);
|
|
39
39
|
|
|
40
|
-
const
|
|
40
|
+
const _totalHeight = $derived(items.length * rowHeight);
|
|
41
41
|
|
|
42
42
|
const visibleRange = $derived.by(() => {
|
|
43
43
|
const start = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
|