@svadmin/ui 0.29.0 → 0.29.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/package.json +20 -9
- package/src/README.md +0 -18
- package/src/actions.ts +0 -110
- package/src/app.css +0 -519
- package/src/component-registry.svelte.ts +0 -60
- package/src/components/AICommandBar.svelte +0 -180
- package/src/components/AboutSettings.svelte +0 -106
- package/src/components/AdminApp.svelte +0 -199
- package/src/components/AnimatedCounter.svelte +0 -46
- package/src/components/AnomalyBadge.svelte +0 -65
- package/src/components/AppearanceSettings.svelte +0 -183
- package/src/components/AuditLogDrawer.svelte +0 -93
- package/src/components/AuditLogViewer.svelte +0 -226
- package/src/components/Authenticated.svelte +0 -51
- package/src/components/AutoForm.svelte +0 -242
- package/src/components/AutoSaveIndicator.svelte +0 -35
- package/src/components/AutoTable.svelte +0 -839
- package/src/components/Breadcrumbs.svelte +0 -58
- package/src/components/CanAccess.svelte +0 -23
- package/src/components/CatchAllNavigate.svelte +0 -21
- package/src/components/ChatDialog.svelte +0 -535
- package/src/components/ComboboxField.svelte +0 -129
- package/src/components/CommandPalette.svelte +0 -135
- package/src/components/ConfigErrorScreen.svelte +0 -126
- package/src/components/ConfirmDialog.svelte +0 -53
- package/src/components/CopilotPanel.svelte +0 -163
- package/src/components/CreatePage.svelte +0 -39
- package/src/components/DevTools.svelte +0 -149
- package/src/components/DraggableGrid.svelte +0 -86
- package/src/components/DraggableHeader.svelte +0 -112
- package/src/components/DrawerForm.svelte +0 -35
- package/src/components/EditPage.svelte +0 -52
- package/src/components/EmptyState.svelte +0 -31
- package/src/components/ErrorBoundary.svelte +0 -49
- package/src/components/ErrorComponent.svelte +0 -36
- package/src/components/FieldRenderer.svelte +0 -357
- package/src/components/ForgotPasswordPage.svelte +0 -112
- package/src/components/Header.svelte +0 -94
- package/src/components/InferencerPanel.svelte +0 -192
- package/src/components/InfiniteList.svelte +0 -93
- package/src/components/InlineEdit.svelte +0 -88
- package/src/components/InsightCard.svelte +0 -98
- package/src/components/KeyboardShortcuts.svelte +0 -61
- package/src/components/Layout.svelte +0 -167
- package/src/components/ListPage.svelte +0 -71
- package/src/components/LiveIndicator.svelte +0 -58
- package/src/components/LoginPage.svelte +0 -163
- package/src/components/MarkdownRenderer.svelte +0 -127
- package/src/components/ModalForm.svelte +0 -38
- package/src/components/NavigateToResource.svelte +0 -20
- package/src/components/PageHeader.svelte +0 -39
- package/src/components/PageSkeleton.svelte +0 -71
- package/src/components/PasswordInput.svelte +0 -96
- package/src/components/PermissionMatrix.svelte +0 -185
- package/src/components/ProfilePage.svelte +0 -284
- package/src/components/RegisterPage.svelte +0 -112
- package/src/components/RolesSettings.svelte +0 -146
- package/src/components/SettingsPage.svelte +0 -113
- package/src/components/ShowPage.svelte +0 -73
- package/src/components/Sidebar.svelte +0 -417
- package/src/components/SidebarItem.svelte +0 -129
- package/src/components/SmartSuggest.svelte +0 -158
- package/src/components/StatsCard.svelte +0 -79
- package/src/components/StepsForm.svelte +0 -192
- package/src/components/TaskQueueDrawer.svelte +0 -158
- package/src/components/TenantSwitcher.svelte +0 -84
- package/src/components/ThemedTitle.svelte +0 -32
- package/src/components/Toast.svelte +0 -46
- package/src/components/TooltipButton.svelte +0 -42
- package/src/components/UndoableNotification.svelte +0 -69
- package/src/components/UpdatePasswordPage.svelte +0 -89
- package/src/components/VirtualTable.svelte +0 -76
- package/src/components/VoiceInput.svelte +0 -120
- package/src/components/buttons/CloneButton.svelte +0 -30
- package/src/components/buttons/CreateButton.svelte +0 -29
- package/src/components/buttons/DeleteButton.svelte +0 -59
- package/src/components/buttons/EditButton.svelte +0 -30
- package/src/components/buttons/ExportButton.svelte +0 -30
- package/src/components/buttons/ImportButton.svelte +0 -56
- package/src/components/buttons/ListButton.svelte +0 -23
- package/src/components/buttons/RefreshButton.svelte +0 -36
- package/src/components/buttons/SaveButton.svelte +0 -27
- package/src/components/buttons/ShowButton.svelte +0 -31
- package/src/components/buttons/index.ts +0 -10
- package/src/components/charts/BarChart.svelte +0 -69
- package/src/components/charts/LineChart.svelte +0 -115
- package/src/components/charts/PieChart.svelte +0 -95
- package/src/components/charts/index.ts +0 -3
- package/src/components/fieldComponentMap.ts +0 -79
- package/src/components/fields/ArrayField.svelte +0 -86
- package/src/components/fields/BooleanField.svelte +0 -13
- package/src/components/fields/DateField.svelte +0 -6
- package/src/components/fields/EmailField.svelte +0 -9
- package/src/components/fields/FileField.svelte +0 -14
- package/src/components/fields/ImageField.svelte +0 -9
- package/src/components/fields/JsonField.svelte +0 -72
- package/src/components/fields/MarkdownField.svelte +0 -9
- package/src/components/fields/MultiSelectField.svelte +0 -21
- package/src/components/fields/NumberField.svelte +0 -6
- package/src/components/fields/RelationField.svelte +0 -24
- package/src/components/fields/RichTextField.svelte +0 -35
- package/src/components/fields/SelectField.svelte +0 -14
- package/src/components/fields/TagField.svelte +0 -12
- package/src/components/fields/TextField.svelte +0 -11
- package/src/components/fields/UrlField.svelte +0 -14
- package/src/components/ui/alert/alert-description.svelte +0 -17
- package/src/components/ui/alert/alert-title.svelte +0 -17
- package/src/components/ui/alert/alert.svelte +0 -44
- package/src/components/ui/alert/index.ts +0 -15
- package/src/components/ui/alert-dialog/alert-dialog-content.svelte +0 -29
- package/src/components/ui/alert-dialog/alert-dialog-description.svelte +0 -16
- package/src/components/ui/alert-dialog/alert-dialog-footer.svelte +0 -11
- package/src/components/ui/alert-dialog/alert-dialog-header.svelte +0 -11
- package/src/components/ui/alert-dialog/alert-dialog-overlay.svelte +0 -16
- package/src/components/ui/alert-dialog/alert-dialog-portal.svelte +0 -6
- package/src/components/ui/alert-dialog/alert-dialog-title.svelte +0 -16
- package/src/components/ui/alert-dialog/index.ts +0 -20
- package/src/components/ui/avatar/avatar.svelte +0 -63
- package/src/components/ui/avatar/index.ts +0 -2
- package/src/components/ui/badge/badge.svelte +0 -49
- package/src/components/ui/badge/index.ts +0 -3
- package/src/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +0 -18
- package/src/components/ui/breadcrumb/breadcrumb-item.svelte +0 -15
- package/src/components/ui/breadcrumb/breadcrumb-link.svelte +0 -15
- package/src/components/ui/breadcrumb/breadcrumb-list.svelte +0 -15
- package/src/components/ui/breadcrumb/breadcrumb-page.svelte +0 -18
- package/src/components/ui/breadcrumb/breadcrumb-separator.svelte +0 -22
- package/src/components/ui/breadcrumb/breadcrumb.svelte +0 -11
- package/src/components/ui/breadcrumb/index.ts +0 -7
- package/src/components/ui/button/button.svelte +0 -90
- package/src/components/ui/button/index.ts +0 -18
- package/src/components/ui/card/card-action.svelte +0 -23
- package/src/components/ui/card/card-content.svelte +0 -20
- package/src/components/ui/card/card-description.svelte +0 -20
- package/src/components/ui/card/card-footer.svelte +0 -20
- package/src/components/ui/card/card-header.svelte +0 -23
- package/src/components/ui/card/card-title.svelte +0 -15
- package/src/components/ui/card/card.svelte +0 -22
- package/src/components/ui/card/index.ts +0 -25
- package/src/components/ui/checkbox/checkbox.svelte +0 -39
- package/src/components/ui/checkbox/index.ts +0 -6
- package/src/components/ui/collapsible/index.ts +0 -7
- package/src/components/ui/command/command-dialog.svelte +0 -31
- package/src/components/ui/command/index.ts +0 -2
- package/src/components/ui/context-menu/index.ts +0 -11
- package/src/components/ui/dialog/dialog-close.svelte +0 -11
- package/src/components/ui/dialog/dialog-content.svelte +0 -48
- package/src/components/ui/dialog/dialog-description.svelte +0 -17
- package/src/components/ui/dialog/dialog-footer.svelte +0 -32
- package/src/components/ui/dialog/dialog-header.svelte +0 -20
- package/src/components/ui/dialog/dialog-overlay.svelte +0 -17
- package/src/components/ui/dialog/dialog-portal.svelte +0 -7
- package/src/components/ui/dialog/dialog-title.svelte +0 -17
- package/src/components/ui/dialog/dialog-trigger.svelte +0 -11
- package/src/components/ui/dialog/dialog.svelte +0 -7
- package/src/components/ui/dialog/index.ts +0 -34
- package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +0 -43
- package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +0 -35
- package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +0 -36
- package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +0 -10
- package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +0 -27
- package/src/components/ui/dropdown-menu/dropdown-menu.svelte +0 -45
- package/src/components/ui/dropdown-menu/index.ts +0 -21
- package/src/components/ui/input/index.ts +0 -7
- package/src/components/ui/input/input.svelte +0 -48
- package/src/components/ui/label/index.ts +0 -1
- package/src/components/ui/label/label.svelte +0 -18
- package/src/components/ui/pagination/index.ts +0 -7
- package/src/components/ui/pagination/pagination-content.svelte +0 -11
- package/src/components/ui/pagination/pagination-ellipsis.svelte +0 -12
- package/src/components/ui/pagination/pagination-item.svelte +0 -11
- package/src/components/ui/pagination/pagination-link.svelte +0 -26
- package/src/components/ui/pagination/pagination-next.svelte +0 -22
- package/src/components/ui/pagination/pagination-previous.svelte +0 -22
- package/src/components/ui/pagination/pagination.svelte +0 -11
- package/src/components/ui/popover/index.ts +0 -12
- package/src/components/ui/popover/popover-content.svelte +0 -25
- package/src/components/ui/progress/index.ts +0 -1
- package/src/components/ui/progress/progress.svelte +0 -22
- package/src/components/ui/scroll-area/index.ts +0 -1
- package/src/components/ui/scroll-area/scroll-area.svelte +0 -20
- package/src/components/ui/select/index.ts +0 -2
- package/src/components/ui/select/select.svelte +0 -36
- package/src/components/ui/separator/index.ts +0 -7
- package/src/components/ui/separator/separator.svelte +0 -23
- package/src/components/ui/sheet/index.ts +0 -16
- package/src/components/ui/sheet/sheet-content.svelte +0 -28
- package/src/components/ui/sheet/sheet-description.svelte +0 -22
- package/src/components/ui/sheet/sheet-footer.svelte +0 -22
- package/src/components/ui/sheet/sheet-header.svelte +0 -22
- package/src/components/ui/sheet/sheet-title.svelte +0 -22
- package/src/components/ui/sheet/sheet.svelte +0 -78
- package/src/components/ui/skeleton/index.ts +0 -2
- package/src/components/ui/skeleton/skeleton.svelte +0 -21
- package/src/components/ui/switch/index.ts +0 -2
- package/src/components/ui/switch/switch.svelte +0 -49
- package/src/components/ui/table/index.ts +0 -28
- package/src/components/ui/table/table-body.svelte +0 -15
- package/src/components/ui/table/table-caption.svelte +0 -20
- package/src/components/ui/table/table-cell.svelte +0 -15
- package/src/components/ui/table/table-footer.svelte +0 -20
- package/src/components/ui/table/table-head.svelte +0 -15
- package/src/components/ui/table/table-header.svelte +0 -20
- package/src/components/ui/table/table-row.svelte +0 -15
- package/src/components/ui/table/table.svelte +0 -17
- package/src/components/ui/tabs/index.ts +0 -15
- package/src/components/ui/tabs/tabs-content.svelte +0 -30
- package/src/components/ui/tabs/tabs-list.svelte +0 -26
- package/src/components/ui/tabs/tabs-trigger.svelte +0 -37
- package/src/components/ui/tabs/tabs.svelte +0 -27
- package/src/components/ui/textarea/index.ts +0 -2
- package/src/components/ui/textarea/textarea.svelte +0 -24
- package/src/components/ui/tooltip/index.ts +0 -19
- package/src/components/ui/tooltip/tooltip-content.svelte +0 -52
- package/src/components/ui/tooltip/tooltip-portal.svelte +0 -7
- package/src/components/ui/tooltip/tooltip-provider.svelte +0 -7
- package/src/components/ui/tooltip/tooltip-trigger.svelte +0 -7
- package/src/components/ui/tooltip/tooltip.svelte +0 -10
- package/src/editor-config.svelte.ts +0 -23
- package/src/index.ts +0 -155
- package/src/markdown-deps.d.ts +0 -21
- package/src/router-state.svelte.ts +0 -65
- package/src/types.ts +0 -33
- package/src/utils.ts +0 -28
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { t } from '@svadmin/core/i18n';
|
|
3
|
-
import { getTheme, setTheme, getResolvedTheme, colorThemes, getColorTheme, setColorTheme, getColorPresets } from '@svadmin/core';
|
|
4
|
-
import { getLocale, setLocale, getAvailableLocales } from '@svadmin/core/i18n';
|
|
5
|
-
import * as Card from './ui/card/index.js';
|
|
6
|
-
import { Button } from './ui/button/index.js';
|
|
7
|
-
import { Sun, Moon, Monitor, Check } from '@lucide/svelte';
|
|
8
|
-
|
|
9
|
-
// Theme mode
|
|
10
|
-
const themeOptions = [
|
|
11
|
-
{ value: 'light' as const, icon: Sun, label: 'settings.light' },
|
|
12
|
-
{ value: 'dark' as const, icon: Moon, label: 'settings.dark' },
|
|
13
|
-
{ value: 'system' as const, icon: Monitor, label: 'settings.systemTheme' },
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
let currentTheme = $derived(getTheme());
|
|
17
|
-
let currentColor = $derived(getColorTheme());
|
|
18
|
-
let currentLocale = $derived(getLocale());
|
|
19
|
-
const availableLocales = getAvailableLocales();
|
|
20
|
-
const presets = getColorPresets();
|
|
21
|
-
|
|
22
|
-
// Sidebar density
|
|
23
|
-
const DENSITY_KEY = 'svadmin-sidebar-density';
|
|
24
|
-
let density = $state<'compact' | 'standard'>(
|
|
25
|
-
(typeof localStorage !== 'undefined' ? localStorage.getItem(DENSITY_KEY) : null) as 'compact' | 'standard' ?? 'standard'
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
function setDensity(d: 'compact' | 'standard') {
|
|
29
|
-
density = d;
|
|
30
|
-
if (typeof localStorage !== 'undefined') localStorage.setItem(DENSITY_KEY, d);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Page size
|
|
34
|
-
const PAGE_SIZE_KEY = 'svadmin-default-page-size';
|
|
35
|
-
const pageSizeOptions = [10, 20, 50];
|
|
36
|
-
let pageSize = $state<number>(
|
|
37
|
-
typeof localStorage !== 'undefined' ? parseInt(localStorage.getItem(PAGE_SIZE_KEY) ?? '10', 10) : 10
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
function setPageSize(size: number) {
|
|
41
|
-
pageSize = size;
|
|
42
|
-
if (typeof localStorage !== 'undefined') localStorage.setItem(PAGE_SIZE_KEY, String(size));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Locale display names
|
|
46
|
-
const localeNames: Record<string, string> = {
|
|
47
|
-
'zh-CN': '中文(简体)',
|
|
48
|
-
'en': 'English',
|
|
49
|
-
'ja': '日本語',
|
|
50
|
-
'ko': '한국어',
|
|
51
|
-
};
|
|
52
|
-
</script>
|
|
53
|
-
|
|
54
|
-
<div class="space-y-6">
|
|
55
|
-
<div>
|
|
56
|
-
<h2 class="text-xl font-semibold text-foreground">{t('settings.appearance')}</h2>
|
|
57
|
-
<p class="text-sm text-muted-foreground mt-1">{t('settings.settingsDescription')}</p>
|
|
58
|
-
</div>
|
|
59
|
-
|
|
60
|
-
<!-- Theme Mode -->
|
|
61
|
-
<Card.Card>
|
|
62
|
-
<Card.CardHeader class="pb-3">
|
|
63
|
-
<Card.CardTitle class="text-base">{t('settings.themeMode')}</Card.CardTitle>
|
|
64
|
-
</Card.CardHeader>
|
|
65
|
-
<Card.CardContent>
|
|
66
|
-
<div class="grid grid-cols-3 gap-3">
|
|
67
|
-
{#each themeOptions as opt}
|
|
68
|
-
{@const active = currentTheme === opt.value}
|
|
69
|
-
<button
|
|
70
|
-
class="relative flex flex-col items-center gap-2 rounded-xl border-2 p-4 transition-all duration-200
|
|
71
|
-
{active
|
|
72
|
-
? 'border-primary bg-primary/5 shadow-sm'
|
|
73
|
-
: 'border-border hover:border-muted-foreground/30 hover:bg-accent/50'}"
|
|
74
|
-
onclick={() => setTheme(opt.value)}
|
|
75
|
-
>
|
|
76
|
-
{#if active}
|
|
77
|
-
<div class="absolute top-2 right-2">
|
|
78
|
-
<Check class="h-3.5 w-3.5 text-primary" />
|
|
79
|
-
</div>
|
|
80
|
-
{/if}
|
|
81
|
-
<div class="h-10 w-10 rounded-lg flex items-center justify-center
|
|
82
|
-
{active ? 'bg-primary/10 text-primary' : 'bg-muted text-muted-foreground'}">
|
|
83
|
-
<opt.icon class="h-5 w-5" />
|
|
84
|
-
</div>
|
|
85
|
-
<span class="text-xs font-medium {active ? 'text-primary' : 'text-muted-foreground'}">
|
|
86
|
-
{t(opt.label)}
|
|
87
|
-
</span>
|
|
88
|
-
</button>
|
|
89
|
-
{/each}
|
|
90
|
-
</div>
|
|
91
|
-
</Card.CardContent>
|
|
92
|
-
</Card.Card>
|
|
93
|
-
|
|
94
|
-
<!-- Color Accent -->
|
|
95
|
-
<Card.Card>
|
|
96
|
-
<Card.CardHeader class="pb-3">
|
|
97
|
-
<Card.CardTitle class="text-base">{t('settings.colorAccent')}</Card.CardTitle>
|
|
98
|
-
</Card.CardHeader>
|
|
99
|
-
<Card.CardContent>
|
|
100
|
-
<div class="flex flex-wrap gap-3">
|
|
101
|
-
{#each presets as preset}
|
|
102
|
-
{@const active = currentColor === preset.name}
|
|
103
|
-
<button
|
|
104
|
-
class="group relative h-9 w-9 rounded-full transition-all duration-200 hover:scale-110
|
|
105
|
-
{active ? 'ring-2 ring-offset-2 ring-offset-background scale-110' : 'opacity-70 hover:opacity-100'}"
|
|
106
|
-
style="background-color: {preset.color}; {active ? `--tw-ring-color: ${preset.color}` : ''}"
|
|
107
|
-
onclick={() => setColorTheme(preset.name as Parameters<typeof setColorTheme>[0])}
|
|
108
|
-
title={preset.label}
|
|
109
|
-
>
|
|
110
|
-
{#if active}
|
|
111
|
-
<Check class="h-4 w-4 text-white absolute inset-0 m-auto drop-shadow-sm" />
|
|
112
|
-
{/if}
|
|
113
|
-
</button>
|
|
114
|
-
{/each}
|
|
115
|
-
</div>
|
|
116
|
-
</Card.CardContent>
|
|
117
|
-
</Card.Card>
|
|
118
|
-
|
|
119
|
-
<!-- Interface -->
|
|
120
|
-
<Card.Card>
|
|
121
|
-
<Card.CardHeader class="pb-3">
|
|
122
|
-
<Card.CardTitle class="text-base">{t('settings.interface')}</Card.CardTitle>
|
|
123
|
-
</Card.CardHeader>
|
|
124
|
-
<Card.CardContent class="space-y-5">
|
|
125
|
-
<!-- Language -->
|
|
126
|
-
<div class="flex items-center justify-between">
|
|
127
|
-
<div>
|
|
128
|
-
<p class="text-sm font-medium text-foreground">{t('settings.language')}</p>
|
|
129
|
-
<p class="text-xs text-muted-foreground">{localeNames[currentLocale] ?? currentLocale}</p>
|
|
130
|
-
</div>
|
|
131
|
-
<select
|
|
132
|
-
class="h-9 rounded-md border border-input bg-background px-3 text-sm ring-offset-background
|
|
133
|
-
focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
|
134
|
-
value={currentLocale}
|
|
135
|
-
onchange={(e) => setLocale((e.target as HTMLSelectElement).value)}
|
|
136
|
-
>
|
|
137
|
-
{#each availableLocales as loc}
|
|
138
|
-
<option value={loc}>{localeNames[loc] ?? loc}</option>
|
|
139
|
-
{/each}
|
|
140
|
-
</select>
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
<div class="h-px bg-border"></div>
|
|
144
|
-
|
|
145
|
-
<!-- Sidebar Density -->
|
|
146
|
-
<div class="flex items-center justify-between">
|
|
147
|
-
<div>
|
|
148
|
-
<p class="text-sm font-medium text-foreground">{t('settings.sidebarDensity')}</p>
|
|
149
|
-
</div>
|
|
150
|
-
<div class="flex rounded-lg border border-input overflow-hidden">
|
|
151
|
-
<button
|
|
152
|
-
class="px-3 py-1.5 text-xs font-medium transition-colors
|
|
153
|
-
{density === 'compact' ? 'bg-primary text-primary-foreground' : 'bg-background text-muted-foreground hover:bg-accent'}"
|
|
154
|
-
onclick={() => setDensity('compact')}
|
|
155
|
-
>{t('settings.compact')}</button>
|
|
156
|
-
<button
|
|
157
|
-
class="px-3 py-1.5 text-xs font-medium transition-colors
|
|
158
|
-
{density === 'standard' ? 'bg-primary text-primary-foreground' : 'bg-background text-muted-foreground hover:bg-accent'}"
|
|
159
|
-
onclick={() => setDensity('standard')}
|
|
160
|
-
>{t('settings.standard')}</button>
|
|
161
|
-
</div>
|
|
162
|
-
</div>
|
|
163
|
-
|
|
164
|
-
<div class="h-px bg-border"></div>
|
|
165
|
-
|
|
166
|
-
<!-- Default Page Size -->
|
|
167
|
-
<div class="flex items-center justify-between">
|
|
168
|
-
<div>
|
|
169
|
-
<p class="text-sm font-medium text-foreground">{t('settings.defaultPageSize')}</p>
|
|
170
|
-
</div>
|
|
171
|
-
<div class="flex rounded-lg border border-input overflow-hidden">
|
|
172
|
-
{#each pageSizeOptions as size}
|
|
173
|
-
<button
|
|
174
|
-
class="px-3 py-1.5 text-xs font-mono font-medium transition-colors
|
|
175
|
-
{pageSize === size ? 'bg-primary text-primary-foreground' : 'bg-background text-muted-foreground hover:bg-accent'}"
|
|
176
|
-
onclick={() => setPageSize(size)}
|
|
177
|
-
>{size}</button>
|
|
178
|
-
{/each}
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
</Card.CardContent>
|
|
182
|
-
</Card.Card>
|
|
183
|
-
</div>
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { getAuditLogProvider, t } from '@svadmin/core';
|
|
3
|
-
import type { AuditEntry } from '@svadmin/core';
|
|
4
|
-
import * as Sheet from './ui/sheet/index.js';
|
|
5
|
-
import { History, Loader2 } from '@lucide/svelte';
|
|
6
|
-
|
|
7
|
-
let { open = $bindable(false), resource, recordId } = $props<{
|
|
8
|
-
open: boolean;
|
|
9
|
-
resource: string;
|
|
10
|
-
recordId?: string | number;
|
|
11
|
-
}>();
|
|
12
|
-
|
|
13
|
-
let logs = $state<AuditEntry[]>([]);
|
|
14
|
-
let isLoading = $state(false);
|
|
15
|
-
|
|
16
|
-
$effect(() => {
|
|
17
|
-
if (open) {
|
|
18
|
-
const provider = getAuditLogProvider();
|
|
19
|
-
if (provider) {
|
|
20
|
-
isLoading = true;
|
|
21
|
-
provider.get({ resource, meta: recordId ? { recordId } : undefined })
|
|
22
|
-
.then((data) => {
|
|
23
|
-
logs = data;
|
|
24
|
-
isLoading = false;
|
|
25
|
-
})
|
|
26
|
-
.catch((err) => {
|
|
27
|
-
console.error('[svadmin] Failed to fetch audit logs', err);
|
|
28
|
-
isLoading = false;
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// Action semantic color mapping
|
|
35
|
-
function getActionColor(action: string) {
|
|
36
|
-
switch (action) {
|
|
37
|
-
case 'create': return 'bg-success';
|
|
38
|
-
case 'update': return 'bg-info';
|
|
39
|
-
case 'delete': return 'bg-destructive';
|
|
40
|
-
default: return 'bg-muted-foreground';
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
</script>
|
|
44
|
-
|
|
45
|
-
<Sheet.Root bind:open>
|
|
46
|
-
<Sheet.Content class="w-full sm:max-w-md overflow-y-auto">
|
|
47
|
-
<Sheet.Header>
|
|
48
|
-
<Sheet.Title class="flex items-center gap-2">
|
|
49
|
-
<History class="h-5 w-5" />
|
|
50
|
-
{t('common.history') || 'History'}
|
|
51
|
-
</Sheet.Title>
|
|
52
|
-
<Sheet.Description>
|
|
53
|
-
Audit log and revision history for this record.
|
|
54
|
-
</Sheet.Description>
|
|
55
|
-
</Sheet.Header>
|
|
56
|
-
|
|
57
|
-
<div class="mt-8 space-y-4">
|
|
58
|
-
{#if isLoading}
|
|
59
|
-
<div class="flex justify-center py-8 text-muted-foreground">
|
|
60
|
-
<Loader2 class="h-6 w-6 animate-spin" />
|
|
61
|
-
</div>
|
|
62
|
-
{:else if logs.length === 0}
|
|
63
|
-
<div class="text-center py-10">
|
|
64
|
-
<History class="h-8 w-8 mx-auto text-muted-foreground/30 mb-3" />
|
|
65
|
-
<p class="text-sm text-muted-foreground">
|
|
66
|
-
{t('common.noData') || 'No audit logs found'}
|
|
67
|
-
</p>
|
|
68
|
-
</div>
|
|
69
|
-
{:else}
|
|
70
|
-
<div class="relative border-l border-border ml-3 pl-6 space-y-8">
|
|
71
|
-
{#each logs as log}
|
|
72
|
-
<div class="relative">
|
|
73
|
-
<span class="absolute -left-7 top-1 flex h-2 w-2 items-center justify-center rounded-full ring-4 ring-background {getActionColor(log.action)}"></span>
|
|
74
|
-
<div class="flex flex-col gap-1.5">
|
|
75
|
-
<div class="flex items-center gap-2 justify-between">
|
|
76
|
-
<span class="text-sm font-semibold capitalize">{log.action}</span>
|
|
77
|
-
<span class="text-xs text-muted-foreground">
|
|
78
|
-
{new Date(log.timestamp).toLocaleString()}
|
|
79
|
-
</span>
|
|
80
|
-
</div>
|
|
81
|
-
{#if log.userId}
|
|
82
|
-
<span class="text-xs text-muted-foreground font-mono bg-muted inline-flex px-1.5 py-0.5 rounded w-fit">
|
|
83
|
-
User: {log.userId}
|
|
84
|
-
</span>
|
|
85
|
-
{/if}
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
88
|
-
{/each}
|
|
89
|
-
</div>
|
|
90
|
-
{/if}
|
|
91
|
-
</div>
|
|
92
|
-
</Sheet.Content>
|
|
93
|
-
</Sheet.Root>
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { t } from '@svadmin/core/i18n';
|
|
3
|
-
import { getAuthProvider } from '@svadmin/core';
|
|
4
|
-
import type { AuditLog } from '@svadmin/core';
|
|
5
|
-
import { toast } from '@svadmin/core/toast';
|
|
6
|
-
import * as Table from './ui/table/index.js';
|
|
7
|
-
import { Badge } from './ui/badge/index.js';
|
|
8
|
-
import { Button } from './ui/button/index.js';
|
|
9
|
-
import { Input } from './ui/input/index.js';
|
|
10
|
-
import { Loader2, ChevronLeft, ChevronRight, Search, FileSearch, Eye, X } from '@lucide/svelte';
|
|
11
|
-
|
|
12
|
-
const authProvider = getAuthProvider();
|
|
13
|
-
|
|
14
|
-
let logs = $state<AuditLog[]>([]);
|
|
15
|
-
let total = $state(0);
|
|
16
|
-
let page = $state(1);
|
|
17
|
-
let pageSize = 20;
|
|
18
|
-
let loading = $state(true);
|
|
19
|
-
let error = $state<string | null>(null);
|
|
20
|
-
let searchQuery = $state('');
|
|
21
|
-
|
|
22
|
-
// Snapshot drawer
|
|
23
|
-
let drawerOpen = $state(false);
|
|
24
|
-
let drawerLog = $state<AuditLog | null>(null);
|
|
25
|
-
|
|
26
|
-
async function loadLogs() {
|
|
27
|
-
if (!authProvider?.getAuditLogs) {
|
|
28
|
-
error = t('settings.auditNotSupported') ?? 'Audit logs are not supported by the current AuthProvider';
|
|
29
|
-
loading = false;
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
loading = true;
|
|
33
|
-
try {
|
|
34
|
-
const result = await authProvider.getAuditLogs({ page, pageSize });
|
|
35
|
-
logs = result.data;
|
|
36
|
-
total = result.total;
|
|
37
|
-
} catch (e) {
|
|
38
|
-
toast.error((e as Error).message);
|
|
39
|
-
} finally {
|
|
40
|
-
loading = false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
$effect(() => {
|
|
45
|
-
loadLogs();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
let totalPages = $derived(Math.ceil(total / pageSize) || 1);
|
|
49
|
-
|
|
50
|
-
let filteredLogs = $derived(
|
|
51
|
-
searchQuery.trim()
|
|
52
|
-
? logs.filter(l =>
|
|
53
|
-
(l.userName ?? '').toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
54
|
-
l.action.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
55
|
-
(l.resource ?? '').toLowerCase().includes(searchQuery.toLowerCase())
|
|
56
|
-
)
|
|
57
|
-
: logs
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
function openSnapshot(log: AuditLog) {
|
|
61
|
-
drawerLog = log;
|
|
62
|
-
drawerOpen = true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function formatDate(d: string | Date) {
|
|
66
|
-
return new Date(d).toLocaleString();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function getActionColor(action: string): 'default' | 'secondary' | 'destructive' | 'outline' {
|
|
70
|
-
const a = action.toLowerCase();
|
|
71
|
-
if (a.includes('delete') || a.includes('remove')) return 'destructive';
|
|
72
|
-
if (a.includes('create') || a.includes('add')) return 'default';
|
|
73
|
-
if (a.includes('update') || a.includes('edit')) return 'secondary';
|
|
74
|
-
return 'outline';
|
|
75
|
-
}
|
|
76
|
-
</script>
|
|
77
|
-
|
|
78
|
-
<div class="h-full flex flex-col">
|
|
79
|
-
<!-- Header -->
|
|
80
|
-
<div class="mb-6">
|
|
81
|
-
<h2 class="text-xl font-semibold text-foreground flex items-center gap-2">
|
|
82
|
-
<FileSearch class="h-5 w-5 text-muted-foreground" />
|
|
83
|
-
{t('settings.auditLogs') ?? 'Audit Logs'}
|
|
84
|
-
</h2>
|
|
85
|
-
<p class="text-sm text-muted-foreground mt-1">
|
|
86
|
-
{t('settings.auditDescription') ?? 'Track and review all system operations for compliance and debugging.'}
|
|
87
|
-
</p>
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
{#if error}
|
|
91
|
-
<div class="p-6 border border-destructive/20 bg-destructive/5 text-destructive rounded-lg text-center">
|
|
92
|
-
<p>{error}</p>
|
|
93
|
-
</div>
|
|
94
|
-
{:else}
|
|
95
|
-
<!-- Search Bar -->
|
|
96
|
-
<div class="mb-4 relative">
|
|
97
|
-
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
98
|
-
<Input
|
|
99
|
-
bind:value={searchQuery}
|
|
100
|
-
placeholder={t('settings.searchLogs') ?? 'Search by user, action, or resource...'}
|
|
101
|
-
class="pl-9"
|
|
102
|
-
/>
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
<!-- Table -->
|
|
106
|
-
<div class="flex-1 shadow-sm ring-1 ring-border/10 rounded-lg bg-card overflow-hidden">
|
|
107
|
-
{#if loading}
|
|
108
|
-
<div class="flex items-center justify-center h-48 text-muted-foreground">
|
|
109
|
-
<Loader2 class="h-5 w-5 animate-spin mr-2" />
|
|
110
|
-
{t('common.loading')}
|
|
111
|
-
</div>
|
|
112
|
-
{:else}
|
|
113
|
-
<div class="overflow-auto">
|
|
114
|
-
<Table.Root>
|
|
115
|
-
<Table.Header>
|
|
116
|
-
<Table.Row>
|
|
117
|
-
<Table.Head class="w-[160px]">{t('settings.auditTime') ?? 'Time'}</Table.Head>
|
|
118
|
-
<Table.Head>{t('settings.auditUser') ?? 'User'}</Table.Head>
|
|
119
|
-
<Table.Head>{t('settings.auditAction') ?? 'Action'}</Table.Head>
|
|
120
|
-
<Table.Head>{t('settings.auditResource') ?? 'Resource'}</Table.Head>
|
|
121
|
-
<Table.Head class="w-[120px]">{t('settings.auditIp') ?? 'IP Address'}</Table.Head>
|
|
122
|
-
<Table.Head class="w-[60px] text-center">{t('common.detail') ?? 'Detail'}</Table.Head>
|
|
123
|
-
</Table.Row>
|
|
124
|
-
</Table.Header>
|
|
125
|
-
<Table.Body>
|
|
126
|
-
{#each filteredLogs as log}
|
|
127
|
-
<Table.Row class="border-0 even:bg-muted/20 hover:bg-muted/40 transition-colors">
|
|
128
|
-
<Table.Cell class="text-xs text-muted-foreground font-mono">
|
|
129
|
-
{formatDate(log.createdAt)}
|
|
130
|
-
</Table.Cell>
|
|
131
|
-
<Table.Cell class="text-sm font-medium">{log.userName ?? log.userId ?? '—'}</Table.Cell>
|
|
132
|
-
<Table.Cell>
|
|
133
|
-
<Badge variant={getActionColor(log.action)}>{log.action}</Badge>
|
|
134
|
-
</Table.Cell>
|
|
135
|
-
<Table.Cell class="text-sm text-muted-foreground">{log.resource ?? '—'}</Table.Cell>
|
|
136
|
-
<Table.Cell class="text-xs text-muted-foreground font-mono">{log.ipAddress ?? '—'}</Table.Cell>
|
|
137
|
-
<Table.Cell class="text-center">
|
|
138
|
-
{#if log.details}
|
|
139
|
-
<Button variant="ghost" size="icon" class="h-7 w-7" onclick={() => openSnapshot(log)}>
|
|
140
|
-
<Eye class="h-3.5 w-3.5" />
|
|
141
|
-
</Button>
|
|
142
|
-
{/if}
|
|
143
|
-
</Table.Cell>
|
|
144
|
-
</Table.Row>
|
|
145
|
-
{:else}
|
|
146
|
-
<Table.Row>
|
|
147
|
-
<Table.Cell colspan={6} class="h-24 text-center text-muted-foreground">
|
|
148
|
-
{t('common.noData')}
|
|
149
|
-
</Table.Cell>
|
|
150
|
-
</Table.Row>
|
|
151
|
-
{/each}
|
|
152
|
-
</Table.Body>
|
|
153
|
-
</Table.Root>
|
|
154
|
-
</div>
|
|
155
|
-
{/if}
|
|
156
|
-
</div>
|
|
157
|
-
|
|
158
|
-
<!-- Pagination -->
|
|
159
|
-
<div class="mt-4 flex items-center justify-between text-sm text-muted-foreground">
|
|
160
|
-
<span>{total} {t('settings.auditTotal') ?? 'total records'}</span>
|
|
161
|
-
<div class="flex items-center gap-2">
|
|
162
|
-
<Button
|
|
163
|
-
variant="outline" size="sm"
|
|
164
|
-
disabled={page <= 1}
|
|
165
|
-
onclick={() => { page--; loadLogs(); }}
|
|
166
|
-
>
|
|
167
|
-
<ChevronLeft class="h-4 w-4" />
|
|
168
|
-
</Button>
|
|
169
|
-
<span>{page} / {totalPages}</span>
|
|
170
|
-
<Button
|
|
171
|
-
variant="outline" size="sm"
|
|
172
|
-
disabled={page >= totalPages}
|
|
173
|
-
onclick={() => { page++; loadLogs(); }}
|
|
174
|
-
>
|
|
175
|
-
<ChevronRight class="h-4 w-4" />
|
|
176
|
-
</Button>
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
{/if}
|
|
180
|
-
</div>
|
|
181
|
-
|
|
182
|
-
<!-- Snapshot Drawer -->
|
|
183
|
-
{#if drawerOpen && drawerLog}
|
|
184
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
185
|
-
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
186
|
-
<div class="fixed inset-0 z-[100] flex justify-end bg-black/40 backdrop-blur-sm" onclick={() => drawerOpen = false}>
|
|
187
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
188
|
-
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
189
|
-
<div class="bg-card shadow-2xl w-full max-w-2xl h-full flex flex-col animate-in slide-in-from-right duration-300" onclick={(e) => e.stopPropagation()}>
|
|
190
|
-
<!-- Drawer Header -->
|
|
191
|
-
<div class="p-6 shadow-[0_1px_0_rgba(0,0,0,0.05)] dark:shadow-[0_1px_0_rgba(255,255,255,0.05)] flex items-center justify-between bg-muted/30">
|
|
192
|
-
<div>
|
|
193
|
-
<h2 class="text-lg font-bold text-foreground flex items-center gap-2">
|
|
194
|
-
<FileSearch class="h-5 w-5 text-primary" />
|
|
195
|
-
{t('settings.auditSnapshot') ?? 'Change Snapshot'}
|
|
196
|
-
</h2>
|
|
197
|
-
<p class="text-xs text-muted-foreground mt-1">{formatDate(drawerLog.createdAt)} · {drawerLog.userName ?? drawerLog.userId}</p>
|
|
198
|
-
</div>
|
|
199
|
-
<button onclick={() => drawerOpen = false} class="text-muted-foreground hover:text-foreground transition p-2 rounded-lg hover:bg-muted">
|
|
200
|
-
<X class="w-5 h-5" />
|
|
201
|
-
</button>
|
|
202
|
-
</div>
|
|
203
|
-
|
|
204
|
-
<!-- Meta Tags -->
|
|
205
|
-
<div class="px-6 py-3 shadow-[0_1px_0_rgba(0,0,0,0.05)] dark:shadow-[0_1px_0_rgba(255,255,255,0.05)] flex items-center gap-2 flex-wrap">
|
|
206
|
-
<Badge variant="outline">{drawerLog.action}</Badge>
|
|
207
|
-
{#if drawerLog.resource}
|
|
208
|
-
<Badge variant="secondary">{drawerLog.resource}</Badge>
|
|
209
|
-
{/if}
|
|
210
|
-
{#if drawerLog.ipAddress}
|
|
211
|
-
<Badge variant="outline" class="font-mono text-xs">{drawerLog.ipAddress}</Badge>
|
|
212
|
-
{/if}
|
|
213
|
-
</div>
|
|
214
|
-
|
|
215
|
-
<!-- JSON Diff Viewer -->
|
|
216
|
-
<div class="flex-1 p-6 overflow-y-auto bg-foreground/[0.03] dark:bg-background/50">
|
|
217
|
-
<pre class="whitespace-pre-wrap text-xs font-mono leading-relaxed text-foreground/80 bg-muted/30 rounded-lg p-4 border border-border">{typeof drawerLog.details === 'object' ? JSON.stringify(drawerLog.details, null, 2) : drawerLog.details}</pre>
|
|
218
|
-
</div>
|
|
219
|
-
|
|
220
|
-
<!-- Footer -->
|
|
221
|
-
<div class="p-4 shadow-[0_-1px_0_rgba(0,0,0,0.05)] dark:shadow-[0_-1px_0_rgba(255,255,255,0.05)] bg-muted/20 text-right text-[11px] text-muted-foreground font-medium">
|
|
222
|
-
System audit snapshot · Read-only
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
|
-
{/if}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { useIsAuthenticated } from '@svadmin/core';
|
|
3
|
-
import type { Snippet } from 'svelte';
|
|
4
|
-
import { navigate } from '@svadmin/core';
|
|
5
|
-
|
|
6
|
-
let {
|
|
7
|
-
children,
|
|
8
|
-
fallback,
|
|
9
|
-
loading,
|
|
10
|
-
redirectTo = '/login',
|
|
11
|
-
appendCurrentPathToQuery = true,
|
|
12
|
-
} = $props<{
|
|
13
|
-
children?: Snippet;
|
|
14
|
-
fallback?: Snippet;
|
|
15
|
-
loading?: Snippet;
|
|
16
|
-
redirectTo?: string;
|
|
17
|
-
appendCurrentPathToQuery?: boolean;
|
|
18
|
-
}>();
|
|
19
|
-
|
|
20
|
-
const isAuth = useIsAuthenticated();
|
|
21
|
-
|
|
22
|
-
function triggerRedirect() {
|
|
23
|
-
if (fallback) return;
|
|
24
|
-
let url = redirectTo;
|
|
25
|
-
if (appendCurrentPathToQuery && typeof window !== 'undefined') {
|
|
26
|
-
const current = encodeURIComponent(window.location.pathname + window.location.search);
|
|
27
|
-
url += `?to=${current}`;
|
|
28
|
-
}
|
|
29
|
-
navigate(url);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
$effect(() => {
|
|
33
|
-
if (!isAuth.isLoading && !isAuth.isAuthenticated) {
|
|
34
|
-
triggerRedirect();
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
</script>
|
|
38
|
-
|
|
39
|
-
{#if isAuth.isLoading}
|
|
40
|
-
{#if loading}
|
|
41
|
-
{@render loading()}
|
|
42
|
-
{/if}
|
|
43
|
-
{:else if isAuth.isAuthenticated}
|
|
44
|
-
{#if children}
|
|
45
|
-
{@render children()}
|
|
46
|
-
{/if}
|
|
47
|
-
{:else}
|
|
48
|
-
{#if fallback}
|
|
49
|
-
{@render fallback()}
|
|
50
|
-
{/if}
|
|
51
|
-
{/if}
|