@svadmin/ui 0.34.9 → 0.35.0

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.
Files changed (123) hide show
  1. package/dist/app.css +372 -0
  2. package/dist/component-registry.svelte.d.ts +1 -0
  3. package/dist/component-registry.svelte.js +1 -0
  4. package/dist/components/AICommandBar.svelte +3 -3
  5. package/dist/components/AboutSettings.svelte +2 -2
  6. package/dist/components/AdminApp.svelte +96 -18
  7. package/dist/components/AdminApp.svelte.d.ts +32 -2
  8. package/dist/components/AnomalyBadge.svelte +1 -1
  9. package/dist/components/ApiSettings.svelte +191 -0
  10. package/dist/components/ApiSettings.svelte.d.ts +3 -0
  11. package/dist/components/AppearanceSettings.svelte +5 -6
  12. package/dist/components/AuditLogDrawer.svelte +1 -1
  13. package/dist/components/AuditLogViewer.svelte +1 -1
  14. package/dist/components/AutoForm.svelte +50 -44
  15. package/dist/components/AutoTable.svelte +44 -39
  16. package/dist/components/Breadcrumbs.svelte +1 -0
  17. package/dist/components/ChatDialog.svelte +5 -4
  18. package/dist/components/ComboboxField.svelte +3 -3
  19. package/dist/components/CommandPalette.svelte +3 -3
  20. package/dist/components/CopilotPanel.svelte +2 -2
  21. package/dist/components/CreatePage.svelte +0 -1
  22. package/dist/components/DevTools.svelte +4 -3
  23. package/dist/components/DraggableHeader.svelte +2 -1
  24. package/dist/components/EditPage.svelte +1 -1
  25. package/dist/components/EmptyState.svelte +1 -1
  26. package/dist/components/ErrorPage.svelte +58 -0
  27. package/dist/components/ErrorPage.svelte.d.ts +8 -0
  28. package/dist/components/FieldRenderer.svelte +9 -8
  29. package/dist/components/ForgotPasswordPage.svelte +111 -68
  30. package/dist/components/Header.svelte +7 -7
  31. package/dist/components/InferencerPanel.svelte +4 -3
  32. package/dist/components/InfiniteList.svelte +3 -4
  33. package/dist/components/IntegrationsSettings.svelte +70 -0
  34. package/dist/components/IntegrationsSettings.svelte.d.ts +4 -0
  35. package/dist/components/KeyboardShortcuts.svelte +3 -3
  36. package/dist/components/Layout.svelte +13 -13
  37. package/dist/components/LoginPage.svelte +207 -105
  38. package/dist/components/LoginPage.svelte.d.ts +3 -15
  39. package/dist/components/MarkdownRenderer.svelte +3 -5
  40. package/dist/components/ModalForm.svelte +1 -3
  41. package/dist/components/NotificationsSettings.svelte +115 -0
  42. package/dist/components/NotificationsSettings.svelte.d.ts +3 -0
  43. package/dist/components/PageHeader.svelte +9 -7
  44. package/dist/components/PageSkeleton.svelte +4 -4
  45. package/dist/components/PasswordInput.svelte +1 -2
  46. package/dist/components/PermissionMatrix.svelte +5 -5
  47. package/dist/components/ProfilePage.svelte +1 -1
  48. package/dist/components/RegisterPage.svelte +103 -65
  49. package/dist/components/ResourceOperationsPage.svelte +153 -0
  50. package/dist/components/ResourceOperationsPage.svelte.d.ts +37 -0
  51. package/dist/components/RolesSettings.svelte +1 -2
  52. package/dist/components/SecuritySettings.svelte +179 -0
  53. package/dist/components/SecuritySettings.svelte.d.ts +4 -0
  54. package/dist/components/SettingsPage.svelte +121 -55
  55. package/dist/components/SettingsPage.svelte.d.ts +25 -1
  56. package/dist/components/ShowPage.svelte +13 -15
  57. package/dist/components/Sidebar.svelte +107 -58
  58. package/dist/components/SidebarItem.svelte +38 -14
  59. package/dist/components/StepsForm.svelte +1 -1
  60. package/dist/components/TaskList.svelte +1 -1
  61. package/dist/components/TaskQueueDrawer.svelte +2 -1
  62. package/dist/components/TenantSwitcher.svelte +1 -2
  63. package/dist/components/Toast.svelte +1 -0
  64. package/dist/components/TooltipButton.svelte +1 -0
  65. package/dist/components/TwoFactorAuthPage.svelte +247 -0
  66. package/dist/components/TwoFactorAuthPage.svelte.d.ts +3 -0
  67. package/dist/components/UpdatePasswordPage.svelte +86 -48
  68. package/dist/components/VirtualTable.svelte +1 -1
  69. package/dist/components/VoiceInput.svelte +1 -0
  70. package/dist/components/account/CompanyProfilePage.svelte +99 -0
  71. package/dist/components/account/CompanyProfilePage.svelte.d.ts +3 -0
  72. package/dist/components/account/GetStartedPage.svelte +123 -0
  73. package/dist/components/account/GetStartedPage.svelte.d.ts +4 -0
  74. package/dist/components/account/ImportMembersPage.svelte +143 -0
  75. package/dist/components/account/ImportMembersPage.svelte.d.ts +3 -0
  76. package/dist/components/account/MembersStarterPage.svelte +104 -0
  77. package/dist/components/account/MembersStarterPage.svelte.d.ts +3 -0
  78. package/dist/components/account/SecurityLogPage.svelte +108 -0
  79. package/dist/components/account/SecurityLogPage.svelte.d.ts +3 -0
  80. package/dist/components/account/SettingsEnterprisePage.svelte +131 -0
  81. package/dist/components/account/SettingsEnterprisePage.svelte.d.ts +3 -0
  82. package/dist/components/account/SettingsPlainPage.svelte +81 -0
  83. package/dist/components/account/SettingsPlainPage.svelte.d.ts +3 -0
  84. package/dist/components/account/SettingsSidebarPage.svelte +98 -0
  85. package/dist/components/account/SettingsSidebarPage.svelte.d.ts +3 -0
  86. package/dist/components/account/TeamMembersPage.svelte +98 -0
  87. package/dist/components/account/TeamMembersPage.svelte.d.ts +3 -0
  88. package/dist/components/buttons/ImportButton.svelte +1 -1
  89. package/dist/components/buttons/ListButton.svelte +1 -1
  90. package/dist/components/charts/BarChart.svelte +1 -1
  91. package/dist/components/charts/LineChart.svelte +3 -3
  92. package/dist/components/charts/PieChart.svelte +2 -2
  93. package/dist/components/fieldComponentMap.js +2 -1
  94. package/dist/components/fields/ArrayField.svelte +1 -1
  95. package/dist/components/fields/MarkdownField.svelte +1 -0
  96. package/dist/components/fields/MultiSelectField.svelte +1 -1
  97. package/dist/components/fields/TagField.svelte +1 -1
  98. package/dist/components/network/TeamCrewTablePage.svelte +122 -0
  99. package/dist/components/network/TeamCrewTablePage.svelte.d.ts +3 -0
  100. package/dist/components/network/UserCardsNFTPage.svelte +134 -0
  101. package/dist/components/network/UserCardsNFTPage.svelte.d.ts +3 -0
  102. package/dist/components/profile/ActivityTimeline.svelte +77 -0
  103. package/dist/components/profile/ActivityTimeline.svelte.d.ts +16 -0
  104. package/dist/components/profile/ProfileCard.svelte +216 -0
  105. package/dist/components/profile/ProfileCard.svelte.d.ts +27 -0
  106. package/dist/components/profile/ProjectsGrid.svelte +91 -0
  107. package/dist/components/profile/ProjectsGrid.svelte.d.ts +17 -0
  108. package/dist/components/profile/PublicProfilePage.svelte +179 -0
  109. package/dist/components/profile/PublicProfilePage.svelte.d.ts +10 -0
  110. package/dist/components/profile/TeamsShowcase.svelte +98 -0
  111. package/dist/components/profile/TeamsShowcase.svelte.d.ts +19 -0
  112. package/dist/components/ui/alert/index.js +1 -0
  113. package/dist/components/ui/badge/index.js +1 -0
  114. package/dist/components/ui/button/button.svelte +1 -0
  115. package/dist/components/ui/button/index.js +1 -0
  116. package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +1 -0
  117. package/dist/components/ui/dropdown-menu/dropdown-menu.svelte +1 -1
  118. package/dist/components/ui/sheet/sheet.svelte +1 -0
  119. package/dist/components/ui/tabs/tabs.svelte +1 -0
  120. package/dist/index.d.ts +23 -0
  121. package/dist/index.js +27 -0
  122. package/dist/router-state.svelte.js +17 -0
  123. package/package.json +13 -13
@@ -0,0 +1,143 @@
1
+ <script lang="ts">
2
+ import { t } from '@svadmin/core/i18n';
3
+ import * as Card from '../ui/card/index.js';
4
+ import { Button } from '../ui/button/index.js';
5
+ import { Upload, Download, FileText, CheckCircle2, Loader2 } from '@lucide/svelte';
6
+
7
+ let isDragging = $state(false);
8
+ let fileName = $state('');
9
+ let importState = $state<'idle' | 'uploading' | 'processing' | 'complete' | 'error'>('idle');
10
+ let progress = $state(0);
11
+ let importCount = $state(0);
12
+
13
+ function handleDragOver(e: DragEvent) {
14
+ e.preventDefault();
15
+ isDragging = true;
16
+ }
17
+
18
+ function handleDragLeave() {
19
+ isDragging = false;
20
+ }
21
+
22
+ function handleDrop(e: DragEvent) {
23
+ e.preventDefault();
24
+ isDragging = false;
25
+ const file = e.dataTransfer?.files?.[0];
26
+ if (file) processFile(file);
27
+ }
28
+
29
+ function handleFileSelect(e: Event) {
30
+ const file = (e.target as HTMLInputElement).files?.[0];
31
+ if (file) processFile(file);
32
+ }
33
+
34
+ function processFile(file: File) {
35
+ fileName = file.name;
36
+ importState = 'uploading';
37
+ progress = 0;
38
+
39
+ // Simulate upload
40
+ const interval = setInterval(() => {
41
+ progress += 10;
42
+ if (progress >= 100) {
43
+ clearInterval(interval);
44
+ importState = 'processing';
45
+ // Simulate processing
46
+ setTimeout(() => {
47
+ importCount = Math.floor(Math.random() * 50) + 20;
48
+ importState = 'complete';
49
+ }, 1500);
50
+ }
51
+ }, 200);
52
+ }
53
+
54
+ function resetImport() {
55
+ fileName = '';
56
+ importState = 'idle';
57
+ progress = 0;
58
+ importCount = 0;
59
+ }
60
+ </script>
61
+
62
+ <div class="space-y-6">
63
+ <div>
64
+ <h2 class="text-xl font-semibold text-foreground">{t('account.importMembers')}</h2>
65
+ <p class="mt-1 text-sm text-muted-foreground">{t('account.importMembersDescription')}</p>
66
+ </div>
67
+
68
+ {#if importState === 'complete'}
69
+ <Card.Card class="border-success/30 bg-success/5">
70
+ <Card.CardContent class="flex flex-col items-center gap-4 p-8">
71
+ <div class="flex h-14 w-14 items-center justify-center rounded-2xl bg-success/10 text-success">
72
+ <CheckCircle2 class="h-7 w-7" />
73
+ </div>
74
+ <div class="text-center">
75
+ <h3 class="text-lg font-semibold text-success">{t('account.importComplete')}</h3>
76
+ <p class="text-sm text-success/70 mt-1">{t('account.imported', { count: importCount })}</p>
77
+ </div>
78
+ <Button variant="outline" onclick={resetImport}>{t('common.back')}</Button>
79
+ </Card.CardContent>
80
+ </Card.Card>
81
+ {:else}
82
+ <!-- Upload area -->
83
+ <Card.Card class="border-border/60">
84
+ <Card.CardContent class="p-6">
85
+ <label
86
+ for="file-input"
87
+ class="block cursor-pointer rounded-xl border-2 border-dashed p-8 text-center transition-colors
88
+ {isDragging ? 'border-primary bg-primary/5' : 'border-border/60 hover:border-primary/40'}"
89
+ ondragover={handleDragOver}
90
+ ondragleave={handleDragLeave}
91
+ ondrop={handleDrop}
92
+ >
93
+ {#if importState === 'idle'}
94
+ <Upload class="mx-auto h-10 w-10 text-muted-foreground" />
95
+ <p class="mt-3 text-sm font-medium text-foreground">{t('account.dragOrClick')}</p>
96
+ <p class="mt-1 text-xs text-muted-foreground">{t('account.supportedFormats')}</p>
97
+ <div class="mt-4">
98
+ <span class="inline-flex h-8 items-center justify-center rounded-md border border-input bg-background px-3 text-sm font-medium shadow-xs transition-colors hover:bg-accent hover:text-accent-foreground">
99
+ {t('common.upload')}
100
+ </span>
101
+ <input id="file-input" type="file" accept=".csv,.xlsx" class="sr-only" onchange={handleFileSelect} />
102
+ </div>
103
+ {:else if importState === 'uploading' || importState === 'processing'}
104
+ <div class="space-y-3">
105
+ <div class="flex items-center justify-center gap-2 text-sm">
106
+ {#if importState === 'processing'}
107
+ <Loader2 class="h-4 w-4 animate-spin" />
108
+ {:else}
109
+ <FileText class="h-4 w-4 text-muted-foreground" />
110
+ {/if}
111
+ <span class="font-medium">{fileName}</span>
112
+ </div>
113
+ <div class="h-2 w-48 mx-auto rounded-full bg-muted overflow-hidden">
114
+ <div class="h-full rounded-full bg-primary transition-all duration-300" style="width: {progress}%"></div>
115
+ </div>
116
+ <p class="text-xs text-muted-foreground">
117
+ {importState === 'uploading' ? t('account.importProgress') : t('account.startImport')}...
118
+ </p>
119
+ </div>
120
+ {/if}
121
+ </label>
122
+ </Card.CardContent>
123
+ </Card.Card>
124
+
125
+ <!-- Template download -->
126
+ <Card.Card class="border-border/60">
127
+ <Card.CardContent class="flex items-center justify-between p-4">
128
+ <div class="flex items-center gap-3">
129
+ <div class="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 text-primary">
130
+ <FileText class="h-4 w-4" />
131
+ </div>
132
+ <div>
133
+ <p class="text-sm font-medium text-foreground">{t('account.downloadTemplate')}</p>
134
+ <p class="text-xs text-muted-foreground">CSV template.csv</p>
135
+ </div>
136
+ </div>
137
+ <Button variant="outline" size="sm">
138
+ <Download class="h-3.5 w-3.5 mr-1" />{t('account.downloadTemplate')}
139
+ </Button>
140
+ </Card.CardContent>
141
+ </Card.Card>
142
+ {/if}
143
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const ImportMembersPage: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type ImportMembersPage = ReturnType<typeof ImportMembersPage>;
3
+ export default ImportMembersPage;
@@ -0,0 +1,104 @@
1
+ <script lang="ts">
2
+ import { t } from '@svadmin/core/i18n';
3
+ import * as Card from '../ui/card/index.js';
4
+ import { Button } from '../ui/button/index.js';
5
+ import { Badge } from '../ui/badge/index.js';
6
+ import { Avatar } from '../ui/avatar/index.js';
7
+ import { Input } from '../ui/input/index.js';
8
+ import { Search, UserPlus, MoreHorizontal, Clock, CheckCircle2, XCircle } from '@lucide/svelte';
9
+
10
+ interface Member {
11
+ id: string;
12
+ name: string;
13
+ email: string;
14
+ role: string;
15
+ status: 'active' | 'invited' | 'inactive';
16
+ avatar?: string;
17
+ joinedAt: string;
18
+ }
19
+
20
+ let searchQuery = $state('');
21
+
22
+ let members = $state<Member[]>([
23
+ { id: '1', name: 'Alex Chen', email: 'alex@acme.com', role: 'Admin', status: 'active', joinedAt: '2023-01-15' },
24
+ { id: '2', name: 'Sarah Kim', email: 'sarah@acme.com', role: 'Editor', status: 'active', joinedAt: '2023-03-22' },
25
+ { id: '3', name: 'Mike Johnson', email: 'mike@acme.com', role: 'Viewer', status: 'invited', joinedAt: '2024-01-10' },
26
+ { id: '4', name: 'Lisa Wang', email: 'lisa@acme.com', role: 'Editor', status: 'active', joinedAt: '2023-06-05' },
27
+ { id: '5', name: 'Tom Brown', email: 'tom@acme.com', role: 'Viewer', status: 'inactive', joinedAt: '2022-11-20' },
28
+ { id: '6', name: 'Emma Davis', email: 'emma@acme.com', role: 'Admin', status: 'active', joinedAt: '2023-02-14' },
29
+ ]);
30
+
31
+ const filteredMembers = $derived(
32
+ searchQuery
33
+ ? members.filter(m => m.name.toLowerCase().includes(searchQuery.toLowerCase()) || m.email.toLowerCase().includes(searchQuery.toLowerCase()))
34
+ : members
35
+ );
36
+
37
+ const statusConfig = {
38
+ 'active': { icon: CheckCircle2, color: 'text-green-500', label: t('account.active') },
39
+ 'invited': { icon: Clock, color: 'text-amber-500', label: t('account.invited') },
40
+ 'inactive': { icon: XCircle, color: 'text-muted-foreground', label: t('account.inactive') },
41
+ };
42
+
43
+ const initials = (name: string) => name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
44
+ </script>
45
+
46
+ <div class="space-y-6">
47
+ <div class="flex items-start justify-between gap-4">
48
+ <div>
49
+ <h2 class="text-xl font-semibold text-foreground">{t('account.membersStarter')}</h2>
50
+ <p class="mt-1 text-sm text-muted-foreground">{t('account.membersStarterDescription')}</p>
51
+ </div>
52
+ <Button size="sm">
53
+ <UserPlus class="h-4 w-4 mr-1" />{t('account.inviteMember')}
54
+ </Button>
55
+ </div>
56
+
57
+ <!-- Search -->
58
+ <div class="relative">
59
+ <Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
60
+ <Input
61
+ placeholder={t('common.search')}
62
+ bind:value={searchQuery}
63
+ class="pl-9"
64
+ />
65
+ </div>
66
+
67
+ <!-- Members list -->
68
+ <Card.Card class="border-border/60">
69
+ <Card.CardContent class="p-0">
70
+ <div class="divide-y">
71
+ {#each filteredMembers as member (member.id)}
72
+ {@const sc = statusConfig[member.status]}
73
+ <div class="flex items-center gap-3 p-4 hover:bg-accent/30 transition-colors">
74
+ <div class="shrink-0">
75
+ {#if member.avatar}
76
+ <Avatar class="h-9 w-9"><img src={member.avatar} alt={member.name} /></Avatar>
77
+ {:else}
78
+ <div class="flex h-9 w-9 items-center justify-center rounded-full bg-primary/10 text-primary text-sm font-semibold">
79
+ {initials(member.name)}
80
+ </div>
81
+ {/if}
82
+ </div>
83
+ <div class="flex-1 min-w-0">
84
+ <div class="flex items-center gap-2">
85
+ <span class="text-sm font-medium text-foreground truncate">{member.name}</span>
86
+ <Badge variant="outline" class="text-[10px] shrink-0">{member.role}</Badge>
87
+ </div>
88
+ <p class="text-xs text-muted-foreground truncate">{member.email}</p>
89
+ </div>
90
+ <div class="shrink-0 flex items-center gap-3">
91
+ <span class="flex items-center gap-1 text-xs {sc.color}">
92
+ <sc.icon class="h-3 w-3" />
93
+ {sc.label}
94
+ </span>
95
+ <Button variant="ghost" size="icon-sm">
96
+ <MoreHorizontal class="h-4 w-4" />
97
+ </Button>
98
+ </div>
99
+ </div>
100
+ {/each}
101
+ </div>
102
+ </Card.CardContent>
103
+ </Card.Card>
104
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const MembersStarterPage: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type MembersStarterPage = ReturnType<typeof MembersStarterPage>;
3
+ export default MembersStarterPage;
@@ -0,0 +1,108 @@
1
+ <script lang="ts">
2
+ import { t } from '@svadmin/core/i18n';
3
+ import * as Card from '../ui/card/index.js';
4
+ import { Button } from '../ui/button/index.js';
5
+ import { Input } from '../ui/input/index.js';
6
+ import { Search, Download } from '@lucide/svelte';
7
+
8
+ interface LogEntry {
9
+ id: string;
10
+ event: string;
11
+ eventLabel: string;
12
+ ip: string;
13
+ device: string;
14
+ location: string;
15
+ time: string;
16
+ severity: 'info' | 'warning' | 'critical';
17
+ }
18
+
19
+ let searchQuery = $state('');
20
+ let filterSeverity = $state<'all' | 'info' | 'warning' | 'critical'>('all');
21
+
22
+ const logs: LogEntry[] = [
23
+ { id: '1', event: 'loginSuccess', eventLabel: t('securityLog.loginSuccess'), ip: '192.168.1.102', device: 'Chrome / macOS', location: 'Shanghai, CN', time: '2 minutes ago', severity: 'info' },
24
+ { id: '2', event: 'apiKeyCreated', eventLabel: t('securityLog.apiKeyCreated'), ip: '192.168.1.102', device: 'Chrome / macOS', location: 'Shanghai, CN', time: '1 hour ago', severity: 'info' },
25
+ { id: '3', event: 'loginFailed', eventLabel: t('securityLog.loginFailed'), ip: '203.0.113.42', device: 'Firefox / Windows', location: 'Unknown', time: '3 hours ago', severity: 'warning' },
26
+ { id: '4', event: 'twoFactorEnabled', eventLabel: t('securityLog.twoFactorEnabled'), ip: '192.168.1.102', device: 'Chrome / macOS', location: 'Shanghai, CN', time: '1 day ago', severity: 'info' },
27
+ { id: '5', event: 'passwordChanged', eventLabel: t('securityLog.passwordChanged'), ip: '192.168.1.102', device: 'Chrome / macOS', location: 'Shanghai, CN', time: '2 days ago', severity: 'info' },
28
+ { id: '6', event: 'loginFailed', eventLabel: t('securityLog.loginFailed'), ip: '198.51.100.77', device: 'Safari / iOS', location: 'Beijing, CN', time: '3 days ago', severity: 'critical' },
29
+ { id: '7', event: 'sessionRevoked', eventLabel: t('securityLog.sessionRevoked'), ip: '192.168.1.105', device: 'Safari / iOS', location: 'Shanghai, CN', time: '5 days ago', severity: 'info' },
30
+ { id: '8', event: 'apiKeyRevoked', eventLabel: t('securityLog.apiKeyRevoked'), ip: '192.168.1.102', device: 'Chrome / macOS', location: 'Shanghai, CN', time: '1 week ago', severity: 'warning' },
31
+ ];
32
+
33
+ const filteredLogs = $derived(
34
+ logs.filter(l => {
35
+ if (filterSeverity !== 'all' && l.severity !== filterSeverity) return false;
36
+ if (searchQuery) {
37
+ const q = searchQuery.toLowerCase();
38
+ return l.eventLabel.toLowerCase().includes(q) || l.ip.includes(q) || l.device.toLowerCase().includes(q);
39
+ }
40
+ return true;
41
+ })
42
+ );
43
+
44
+ </script>
45
+
46
+ <div class="space-y-6">
47
+ <div class="flex items-start justify-between gap-4">
48
+ <div>
49
+ <h2 class="text-xl font-semibold text-foreground">{t('account.securityLog')}</h2>
50
+ <p class="mt-1 text-sm text-muted-foreground">{t('account.securityLogDescription')}</p>
51
+ </div>
52
+ <Button variant="outline" size="sm">
53
+ <Download class="h-3.5 w-3.5 mr-1" />{t('common.export')}
54
+ </Button>
55
+ </div>
56
+
57
+ <!-- Filters -->
58
+ <div class="flex flex-wrap gap-3">
59
+ <div class="relative flex-1 min-w-[200px]">
60
+ <Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
61
+ <Input placeholder={t('common.search')} bind:value={searchQuery} class="pl-9" />
62
+ </div>
63
+ <div class="flex rounded-lg border border-input overflow-hidden">
64
+ {#each ['all', 'info', 'warning', 'critical'] as sev (sev)}
65
+ <button
66
+ class="px-3 py-1.5 text-xs font-medium transition-colors capitalize
67
+ {filterSeverity === sev ? 'bg-primary text-primary-foreground' : 'bg-background text-muted-foreground hover:bg-accent'}"
68
+ onclick={() => filterSeverity = sev as typeof filterSeverity}
69
+ >{sev === 'all' ? t('common.filter') : sev}</button>
70
+ {/each}
71
+ </div>
72
+ </div>
73
+
74
+ <!-- Log table -->
75
+ <Card.Card class="border-border/60">
76
+ <Card.CardContent class="p-0">
77
+ <div class="overflow-x-auto">
78
+ <table class="w-full">
79
+ <thead>
80
+ <tr class="border-b bg-muted/30">
81
+ <th class="px-4 py-3 text-left text-xs font-semibold text-muted-foreground">{t('securityLog.event')}</th>
82
+ <th class="px-4 py-3 text-left text-xs font-semibold text-muted-foreground">{t('securityLog.ip')}</th>
83
+ <th class="px-4 py-3 text-left text-xs font-semibold text-muted-foreground">{t('securityLog.device')}</th>
84
+ <th class="px-4 py-3 text-left text-xs font-semibold text-muted-foreground">{t('securityLog.location')}</th>
85
+ <th class="px-4 py-3 text-left text-xs font-semibold text-muted-foreground">{t('securityLog.time')}</th>
86
+ </tr>
87
+ </thead>
88
+ <tbody class="divide-y">
89
+ {#each filteredLogs as log (log.id)}
90
+ <tr class="hover:bg-accent/30 transition-colors">
91
+ <td class="px-4 py-3">
92
+ <div class="flex items-center gap-2">
93
+ <span class="inline-flex h-2 w-2 rounded-full {log.severity === 'critical' ? 'bg-red-500' : log.severity === 'warning' ? 'bg-amber-500' : 'bg-blue-500'}"></span>
94
+ <span class="text-sm text-foreground">{log.eventLabel}</span>
95
+ </div>
96
+ </td>
97
+ <td class="px-4 py-3 text-sm text-muted-foreground font-mono">{log.ip}</td>
98
+ <td class="px-4 py-3 text-sm text-muted-foreground">{log.device}</td>
99
+ <td class="px-4 py-3 text-sm text-muted-foreground">{log.location}</td>
100
+ <td class="px-4 py-3 text-sm text-muted-foreground whitespace-nowrap">{log.time}</td>
101
+ </tr>
102
+ {/each}
103
+ </tbody>
104
+ </table>
105
+ </div>
106
+ </Card.CardContent>
107
+ </Card.Card>
108
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const SecurityLogPage: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type SecurityLogPage = ReturnType<typeof SecurityLogPage>;
3
+ export default SecurityLogPage;
@@ -0,0 +1,131 @@
1
+ <script lang="ts">
2
+ import { t } from '@svadmin/core/i18n';
3
+ import * as Card from '../ui/card/index.js';
4
+ import { Button } from '../ui/button/index.js';
5
+ import { Input } from '../ui/input/index.js';
6
+ import { Label } from '../ui/label/index.js';
7
+ import { Switch } from '../ui/switch/index.js';
8
+ import { Badge } from '../ui/badge/index.js';
9
+ import { Loader2, Shield, Building2, Lock, Globe } from '@lucide/svelte';
10
+
11
+ let ssoEnabled = $state(false);
12
+ let ssoProvider = $state('');
13
+ let domainRestriction = $state('');
14
+ let passwordPolicy = $state('strong');
15
+ let sessionTimeout = $state('30');
16
+ let ipWhitelist = $state('');
17
+ let auditLogging = $state(true);
18
+ let dataRetention = $state('365');
19
+ let saving = $state(false);
20
+
21
+ async function handleSave() {
22
+ saving = true;
23
+ await new Promise(r => setTimeout(r, 800));
24
+ saving = false;
25
+ }
26
+ </script>
27
+
28
+ <div class="space-y-6">
29
+ <div class="flex items-start justify-between gap-4">
30
+ <div>
31
+ <h2 class="text-xl font-semibold text-foreground">{t('account.settingsEnterprise')}</h2>
32
+ <p class="mt-1 text-sm text-muted-foreground">{t('account.settingsEnterpriseDescription')}</p>
33
+ </div>
34
+ <Badge variant="secondary"><Building2 class="h-3 w-3 mr-1" />Enterprise</Badge>
35
+ </div>
36
+
37
+ <!-- SSO -->
38
+ <Card.Card class="border-border/60">
39
+ <Card.CardHeader>
40
+ <Card.CardTitle class="flex items-center gap-2 text-base">
41
+ <Shield class="h-4 w-4 text-muted-foreground" />
42
+ 单点登录 (SSO)
43
+ </Card.CardTitle>
44
+ <Card.CardDescription>配置企业身份提供商以实现统一登录体验。</Card.CardDescription>
45
+ </Card.CardHeader>
46
+ <Card.CardContent class="space-y-4">
47
+ <div class="flex items-center justify-between gap-4">
48
+ <div class="space-y-1">
49
+ <Label for="sso-enabled">启用 SSO</Label>
50
+ <p class="text-xs text-muted-foreground">允许员工通过企业 IdP 登录</p>
51
+ </div>
52
+ <Switch id="sso-enabled" bind:checked={ssoEnabled} />
53
+ </div>
54
+ {#if ssoEnabled}
55
+ <div class="grid gap-4 sm:grid-cols-2">
56
+ <div class="space-y-2">
57
+ <Label for="sso-provider">SSO 提供商</Label>
58
+ <Input id="sso-provider" bind:value={ssoProvider} placeholder="Okta, Azure AD, etc." />
59
+ </div>
60
+ <div class="space-y-2">
61
+ <Label for="domain-restriction">域名限制</Label>
62
+ <Input id="domain-restriction" bind:value={domainRestriction} placeholder="@company.com" />
63
+ </div>
64
+ </div>
65
+ {/if}
66
+ </Card.CardContent>
67
+ </Card.Card>
68
+
69
+ <!-- Security Policy -->
70
+ <Card.Card class="border-border/60">
71
+ <Card.CardHeader>
72
+ <Card.CardTitle class="flex items-center gap-2 text-base">
73
+ <Lock class="h-4 w-4 text-muted-foreground" />
74
+ 安全策略
75
+ </Card.CardTitle>
76
+ </Card.CardHeader>
77
+ <Card.CardContent class="space-y-4">
78
+ <div class="grid gap-4 sm:grid-cols-2">
79
+ <div class="space-y-2">
80
+ <Label for="password-policy">密码策略</Label>
81
+ <select id="password-policy" bind:value={passwordPolicy} class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring">
82
+ <option value="basic">基本</option>
83
+ <option value="strong">强密码</option>
84
+ <option value="very-strong">非常强</option>
85
+ </select>
86
+ </div>
87
+ <div class="space-y-2">
88
+ <Label for="session-timeout">会话超时(分钟)</Label>
89
+ <Input id="session-timeout" bind:value={sessionTimeout} type="number" />
90
+ </div>
91
+ </div>
92
+
93
+ <div class="space-y-2">
94
+ <Label for="ip-whitelist">IP 白名单</Label>
95
+ <Input id="ip-whitelist" bind:value={ipWhitelist} placeholder="10.0.0.0/8, 192.168.1.0/24" />
96
+ <p class="text-xs text-muted-foreground">留空表示不限制 IP</p>
97
+ </div>
98
+ </Card.CardContent>
99
+ </Card.Card>
100
+
101
+ <!-- Data & Compliance -->
102
+ <Card.Card class="border-border/60">
103
+ <Card.CardHeader>
104
+ <Card.CardTitle class="flex items-center gap-2 text-base">
105
+ <Globe class="h-4 w-4 text-muted-foreground" />
106
+ 数据与合规
107
+ </Card.CardTitle>
108
+ </Card.CardHeader>
109
+ <Card.CardContent class="space-y-4">
110
+ <div class="flex items-center justify-between gap-4">
111
+ <div class="space-y-1">
112
+ <Label for="audit-logging">审计日志</Label>
113
+ <p class="text-xs text-muted-foreground">记录所有管理操作以备合规审查</p>
114
+ </div>
115
+ <Switch id="audit-logging" bind:checked={auditLogging} />
116
+ </div>
117
+
118
+ <div class="space-y-2">
119
+ <Label for="data-retention">数据保留天数</Label>
120
+ <Input id="data-retention" bind:value={dataRetention} type="number" />
121
+ </div>
122
+
123
+ <div class="flex justify-end">
124
+ <Button onclick={handleSave} disabled={saving}>
125
+ {#if saving}<Loader2 class="h-4 w-4 animate-spin mr-2" />{/if}
126
+ {t('common.save')}
127
+ </Button>
128
+ </div>
129
+ </Card.CardContent>
130
+ </Card.Card>
131
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const SettingsEnterprisePage: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type SettingsEnterprisePage = ReturnType<typeof SettingsEnterprisePage>;
3
+ export default SettingsEnterprisePage;
@@ -0,0 +1,81 @@
1
+ <script lang="ts">
2
+ import { t } from '@svadmin/core/i18n';
3
+ import * as Card from '../ui/card/index.js';
4
+ import { Button } from '../ui/button/index.js';
5
+ import { Input } from '../ui/input/index.js';
6
+ import { Label } from '../ui/label/index.js';
7
+ import { Switch } from '../ui/switch/index.js';
8
+ import { Separator } from '../ui/separator/index.js';
9
+ import { Loader2 } from '@lucide/svelte';
10
+
11
+ let displayName = $state('Alex Chen');
12
+ let email = $state('alex@example.com');
13
+ let emailNotifications = $state(true);
14
+ let pushNotifications = $state(true);
15
+ let twoFactor = $state(false);
16
+ let saving = $state(false);
17
+
18
+ async function handleSave() {
19
+ saving = true;
20
+ await new Promise(r => setTimeout(r, 800));
21
+ saving = false;
22
+ }
23
+ </script>
24
+
25
+ <div class="space-y-6">
26
+ <div>
27
+ <h2 class="text-xl font-semibold text-foreground">{t('account.settingsPlain')}</h2>
28
+ <p class="mt-1 text-sm text-muted-foreground">{t('account.settingsPlainDescription')}</p>
29
+ </div>
30
+
31
+ <Card.Card class="border-border/60">
32
+ <Card.CardHeader>
33
+ <Card.CardTitle class="text-base">{t('settings.general')}</Card.CardTitle>
34
+ </Card.CardHeader>
35
+ <Card.CardContent class="space-y-4">
36
+ <div class="grid gap-4 sm:grid-cols-2">
37
+ <div class="space-y-2">
38
+ <Label for="display-name">{t('profile.name')}</Label>
39
+ <Input id="display-name" bind:value={displayName} />
40
+ </div>
41
+ <div class="space-y-2">
42
+ <Label for="email">Email</Label>
43
+ <Input id="email" bind:value={email} type="email" />
44
+ </div>
45
+ </div>
46
+
47
+ <Separator />
48
+
49
+ <div class="flex items-center justify-between gap-4">
50
+ <div class="space-y-1">
51
+ <Label for="email-notif">{t('notifications.emailTitle')}</Label>
52
+ <p class="text-xs text-muted-foreground">{t('notifications.emailDescription')}</p>
53
+ </div>
54
+ <Switch id="email-notif" bind:checked={emailNotifications} />
55
+ </div>
56
+
57
+ <div class="flex items-center justify-between gap-4">
58
+ <div class="space-y-1">
59
+ <Label for="push-notif">{t('notifications.pushTitle')}</Label>
60
+ <p class="text-xs text-muted-foreground">{t('notifications.pushDescription')}</p>
61
+ </div>
62
+ <Switch id="push-notif" bind:checked={pushNotifications} />
63
+ </div>
64
+
65
+ <div class="flex items-center justify-between gap-4">
66
+ <div class="space-y-1">
67
+ <Label for="two-factor">{t('security.twoFactorAuth')}</Label>
68
+ <p class="text-xs text-muted-foreground">{t('security.twoFactorDescription')}</p>
69
+ </div>
70
+ <Switch id="two-factor" bind:checked={twoFactor} />
71
+ </div>
72
+
73
+ <div class="flex justify-end">
74
+ <Button onclick={handleSave} disabled={saving}>
75
+ {#if saving}<Loader2 class="h-4 w-4 animate-spin mr-2" />{/if}
76
+ {t('common.save')}
77
+ </Button>
78
+ </div>
79
+ </Card.CardContent>
80
+ </Card.Card>
81
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const SettingsPlainPage: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type SettingsPlainPage = ReturnType<typeof SettingsPlainPage>;
3
+ export default SettingsPlainPage;