@svadmin/core 0.27.0 → 0.28.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.
- package/package.json +8 -8
- package/src/adapters/casl.ts +1 -0
- package/src/auth-hooks.svelte.ts +1 -1
- package/src/data-transfer.svelte.ts +1 -1
- package/src/form-hooks.svelte.ts +8 -8
- package/src/helpers-pure.ts +1 -1
- package/src/helpers.ts +0 -1
- package/src/hooks.svelte.ts +4 -5
- package/src/i18n.svelte.ts +380 -0
- package/src/live-sse.ts +1 -1
- package/src/live-websocket.ts +1 -1
- package/src/mutation-hooks.svelte.ts +12 -11
- package/src/permissions.svelte.ts +1 -1
- package/src/query-hooks.svelte.ts +4 -4
- package/src/routing-hooks.svelte.ts +2 -1
- package/src/table-hooks.svelte.ts +3 -2
- package/src/table-state.svelte.ts +3 -2
- package/src/task-hooks.svelte.ts +2 -2
- package/src/theme.svelte.ts +12 -3
- package/src/toast.svelte.ts +1 -0
- package/src/types.ts +3 -2
- package/src/unsaved-changes.svelte.ts +4 -3
- package/src/useParsed.svelte.ts +2 -0
- package/src/utility-hooks.svelte.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"author": "zuohuadong",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
},
|
|
10
10
|
"main": "src/index.ts",
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@happy-dom/global-registrator": "^20.
|
|
13
|
-
"@sveltejs/vite-plugin-svelte": "^7.
|
|
12
|
+
"@happy-dom/global-registrator": "^20.10.0",
|
|
13
|
+
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
|
14
14
|
"@testing-library/dom": "^10.4.1",
|
|
15
15
|
"@testing-library/svelte": "^5.3.1",
|
|
16
16
|
"@testing-library/user-event": "^14.6.1",
|
|
17
|
-
"@types/bun": "^1.3.
|
|
18
|
-
"@types/jsdom": "^28.0.
|
|
19
|
-
"happy-dom": "^20.
|
|
20
|
-
"jsdom": "^29.
|
|
21
|
-
"vitest": "^4.1.
|
|
17
|
+
"@types/bun": "^1.3.14",
|
|
18
|
+
"@types/jsdom": "^28.0.3",
|
|
19
|
+
"happy-dom": "^20.10.0",
|
|
20
|
+
"jsdom": "^29.1.1",
|
|
21
|
+
"vitest": "^4.1.8"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"svelte": "^5.0.0",
|
package/src/adapters/casl.ts
CHANGED
package/src/auth-hooks.svelte.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getDataProviderForResource } from './context.svelte';
|
|
2
2
|
import { useParsed } from './useParsed.svelte';
|
|
3
|
-
import type {
|
|
3
|
+
import type { Sort, Filter, BaseRecord } from './types';
|
|
4
4
|
|
|
5
5
|
export interface UseExportOptions<TData extends BaseRecord = BaseRecord> {
|
|
6
6
|
resource?: string;
|
package/src/form-hooks.svelte.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable svelte/prefer-svelte-reactivity */
|
|
1
2
|
import { useQueryClient } from '@tanstack/svelte-query';
|
|
2
3
|
import { useParsed } from './useParsed.svelte';
|
|
3
4
|
import { getAdminOptions } from './options.svelte';
|
|
@@ -17,8 +18,8 @@ import { deepMerge, invalidateByScopes } from './mutation-hooks.svelte';
|
|
|
17
18
|
|
|
18
19
|
export interface UseFormOptions<
|
|
19
20
|
TVariables extends Record<string, unknown> = Record<string, unknown>,
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
_TData extends BaseRecord = BaseRecord,
|
|
22
|
+
_TError = HttpError,
|
|
22
23
|
> {
|
|
23
24
|
resource?: KnownResources;
|
|
24
25
|
action?: 'create' | 'edit' | 'clone' | 'show';
|
|
@@ -44,7 +45,7 @@ export interface UseFormOptions<
|
|
|
44
45
|
/** Called every time a field value changes. */
|
|
45
46
|
onChange?: (event: { field: string; value: unknown; values: TVariables }) => void;
|
|
46
47
|
/** Called before mutation. Return false or call cancel() to prevent submission. */
|
|
47
|
-
onSubmit?: (ctx: { values: TVariables; action: 'create' | 'edit' | 'clone' | 'show'; cancel: () => void }) =>
|
|
48
|
+
onSubmit?: (ctx: { values: TVariables; action: 'create' | 'edit' | 'clone' | 'show'; cancel: () => void }) => boolean | undefined;
|
|
48
49
|
|
|
49
50
|
// ─── Meta ─────────────────────────────────────────────────────
|
|
50
51
|
meta?: Record<string, unknown>;
|
|
@@ -85,7 +86,7 @@ export interface UseFormOptions<
|
|
|
85
86
|
|
|
86
87
|
export interface UseFormReturn<
|
|
87
88
|
TVariables extends Record<string, unknown> = Record<string, unknown>,
|
|
88
|
-
|
|
89
|
+
_TData extends BaseRecord = BaseRecord,
|
|
89
90
|
> {
|
|
90
91
|
// ─── Form values (single source of truth) ─────────────────────
|
|
91
92
|
/** Reactive form values. Read/write directly. */
|
|
@@ -213,8 +214,7 @@ export function useForm<
|
|
|
213
214
|
function setFieldError(field: string, message: string) { errors = { ...errors, [field]: message }; }
|
|
214
215
|
function clearErrors() { errors = {}; }
|
|
215
216
|
function clearFieldError(field: string) {
|
|
216
|
-
const
|
|
217
|
-
delete next[field];
|
|
217
|
+
const { [field]: _, ...next } = errors;
|
|
218
218
|
errors = next;
|
|
219
219
|
}
|
|
220
220
|
|
|
@@ -270,7 +270,7 @@ export function useForm<
|
|
|
270
270
|
const query = createQuery(() => ({
|
|
271
271
|
queryKey: [options.dataProviderName, resource, 'one', currentId, queryMeta],
|
|
272
272
|
queryFn: async () => {
|
|
273
|
-
const result = await provider.getOne<BaseRecord>({ resource, id: currentId
|
|
273
|
+
const result = await provider.getOne<BaseRecord>({ resource, id: currentId as string, meta: queryMeta });
|
|
274
274
|
return result;
|
|
275
275
|
},
|
|
276
276
|
enabled: (queryOptions?.enabled ?? true) && (action === 'edit' || action === 'clone' || action === 'show') && currentId != null,
|
|
@@ -345,7 +345,7 @@ export function useForm<
|
|
|
345
345
|
const updateMut = createMutation<{ data: TData }, unknown, TVariables>(() => ({
|
|
346
346
|
...options.updateMutationOptions,
|
|
347
347
|
mutationFn: async (variables: TVariables) => {
|
|
348
|
-
const targetId = currentId
|
|
348
|
+
const targetId = currentId as string;
|
|
349
349
|
const targetResource = resource;
|
|
350
350
|
const targetMeta = mutationMeta;
|
|
351
351
|
if (mutationMode === 'undoable') {
|
package/src/helpers-pure.ts
CHANGED
package/src/helpers.ts
CHANGED
package/src/hooks.svelte.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable svelte/prefer-svelte-reactivity */
|
|
1
2
|
// @svadmin/core — Core Data and Hook APIs (Modularized for v0.2.29+)
|
|
2
3
|
|
|
3
4
|
// ─── Re-exports from modular hook files ─────────────────────────────
|
|
@@ -30,13 +31,11 @@ import { invalidateByScopes, publishLiveEvent } from './mutation-hooks.svelte';
|
|
|
30
31
|
import type { NotificationConfig, OvertimeOptions, LiveSubscriptionParams } from './hook-utils.svelte';
|
|
31
32
|
import type { BaseRecord, HttpError, Pagination, Sort, Filter, DataProvider, KnownResources } from './types';
|
|
32
33
|
import type { LiveMode, LiveEvent } from './live.svelte';
|
|
33
|
-
import { useList } from './query-hooks.svelte';
|
|
34
|
-
import type { UseListOptions } from './query-hooks.svelte';
|
|
35
34
|
import { audit } from './audit';
|
|
36
35
|
|
|
37
36
|
// ─── useInfiniteList ────────────────────────────────────────────────
|
|
38
37
|
|
|
39
|
-
export interface UseInfiniteListOptions<
|
|
38
|
+
export interface UseInfiniteListOptions<_TData extends BaseRecord = BaseRecord, _TError = HttpError> {
|
|
40
39
|
resource?: KnownResources;
|
|
41
40
|
pagination?: Pagination;
|
|
42
41
|
sorters?: Sort[];
|
|
@@ -113,7 +112,7 @@ export function useInfiniteList<TData extends BaseRecord = BaseRecord, TError =
|
|
|
113
112
|
|
|
114
113
|
// ─── useSelect ──────────────────────────────────────────────────────
|
|
115
114
|
|
|
116
|
-
export interface UseSelectOptions<TData extends BaseRecord = BaseRecord,
|
|
115
|
+
export interface UseSelectOptions<TData extends BaseRecord = BaseRecord, _TOption = { label: string; value: string | number }> {
|
|
117
116
|
resource: KnownResources;
|
|
118
117
|
optionLabel?: string | ((item: TData) => string);
|
|
119
118
|
optionValue?: string | ((item: TData) => string | number);
|
|
@@ -231,7 +230,7 @@ export function useSelect<TData extends BaseRecord = BaseRecord, TOption = { lab
|
|
|
231
230
|
|
|
232
231
|
// ─── useCustom ──────────────────────────────────────────────────────
|
|
233
232
|
|
|
234
|
-
export interface UseCustomOptions<
|
|
233
|
+
export interface UseCustomOptions<_TData = unknown, _TError = HttpError> {
|
|
235
234
|
url: string;
|
|
236
235
|
method: 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
237
236
|
config?: { payload?: unknown; query?: Record<string, unknown>; headers?: Record<string, string>; sorters?: Sort[]; filters?: Filter[] };
|
package/src/i18n.svelte.ts
CHANGED
|
@@ -144,6 +144,19 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
144
144
|
'devtools.title': '开发者工具 (Ctrl+Shift+D)',
|
|
145
145
|
// Validation
|
|
146
146
|
// Chat
|
|
147
|
+
'chat.title': 'AI 助手',
|
|
148
|
+
'chat.typing': '输入中...',
|
|
149
|
+
'chat.clear': '清空',
|
|
150
|
+
'chat.welcome': '我可以帮你做什么?',
|
|
151
|
+
'chat.welcomeDesc': '可以询问当前后台、资源和库存运营数据。',
|
|
152
|
+
'chat.thinking': '思考中...',
|
|
153
|
+
'chat.placeholder': '输入消息...',
|
|
154
|
+
'chat.shortcutHint': '切换',
|
|
155
|
+
'chat.error': '抱歉,处理时出现问题,请重试。',
|
|
156
|
+
'chat.approvalRequired': '需要确认',
|
|
157
|
+
'chat.suggestion1': '如何新建一条资源记录?',
|
|
158
|
+
'chat.suggestion2': '解释当前数据模型',
|
|
159
|
+
'chat.suggestion3': '帮我写一个筛选条件',
|
|
147
160
|
'chat.contextInfo': '当前上下文:{resource}',
|
|
148
161
|
'chat.actionButton': '执行',
|
|
149
162
|
'chat.historyRestored': '已恢复聊天记录',
|
|
@@ -162,6 +175,62 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
162
175
|
'settings.appearance': '外观设置',
|
|
163
176
|
'settings.about': '关于',
|
|
164
177
|
'settings.general': '通用',
|
|
178
|
+
'settings.security': '安全设置',
|
|
179
|
+
'settings.integrations': '第三方集成',
|
|
180
|
+
'settings.notifications': '通知设置',
|
|
181
|
+
'settings.api': 'API 设置',
|
|
182
|
+
'settings.developer': '开发者',
|
|
183
|
+
'settings.workspace': '工作区',
|
|
184
|
+
'settings.securityDescription': '管理您的密码、活跃会话和双重身份验证偏好。',
|
|
185
|
+
'settings.integrationsDescription': '连接和管理工作区的第三方服务扩展。',
|
|
186
|
+
'settings.notificationsDescription': '配置系统警报的接收方式和时机。',
|
|
187
|
+
'settings.apiDescription': '生成并管理安全 API 密钥,以便集成外部程序脚本。',
|
|
188
|
+
'security.twoFactorAuth': '双重身份验证 (2FA)',
|
|
189
|
+
'security.twoFactorDescription': '启用后,在输入密码后还需要输入手机验证码,为账户增加额外安全保障。',
|
|
190
|
+
'security.enable2fa': '启用双重身份验证',
|
|
191
|
+
'security.twoFactorActive': '双重身份验证已启用。新登录时将需要验证码。',
|
|
192
|
+
'security.twoFactorInactive': '双重身份验证已禁用。建议开启以保护账户安全。',
|
|
193
|
+
'security.passwordLastChanged': '上次修改:{time}',
|
|
194
|
+
'security.activeSessions': '活跃会话',
|
|
195
|
+
'security.sessionsDescription': '以下是已登录您账户的设备。您可以撤销任何不识别的设备。',
|
|
196
|
+
'security.revokeOthers': '注销其他所有设备',
|
|
197
|
+
'security.currentSession': '当前设备',
|
|
198
|
+
'security.revokeSession': '注销此设备',
|
|
199
|
+
'security.noSessions': '无活跃会话。',
|
|
200
|
+
'integrations.statusConnected': '已连接',
|
|
201
|
+
'integrations.statusDisconnected': '未连接',
|
|
202
|
+
'integrations.enabled': '已启用',
|
|
203
|
+
'integrations.disabled': '已禁用',
|
|
204
|
+
'integrations.addMoreHint': '需要自定义集成?请联系管理员注册新的应用程序。',
|
|
205
|
+
'notifications.emailTitle': '邮件通知',
|
|
206
|
+
'notifications.emailDescription': '将系统自动发送的通知推送到您的电子邮箱。',
|
|
207
|
+
'notifications.securityAlerts': '安全警报',
|
|
208
|
+
'notifications.securityAlertsDesc': '在密码修改、新登录尝试或生成 API 密钥时收到通知。',
|
|
209
|
+
'notifications.activityLogs': '活动报告',
|
|
210
|
+
'notifications.activityLogsDesc': '每周获取关于系统审计、资源修改和后台任务的摘要。',
|
|
211
|
+
'notifications.systemAlerts': '系统报告',
|
|
212
|
+
'notifications.systemAlertsDesc': '在服务器实例或数据流水线发生故障时接收实时通知。',
|
|
213
|
+
'notifications.pushTitle': '应用内与浏览器推送',
|
|
214
|
+
'notifications.pushDescription': '在当前打开的浏览器标签页上接收推送通知。',
|
|
215
|
+
'notifications.securityPushDesc': '在检测到未经授权的登录时立即在浏览器发出警报。',
|
|
216
|
+
'notifications.activityPushDesc': '当同事编辑资源时,即时接收操作日志推送。',
|
|
217
|
+
'notifications.systemPushDesc': '针对任务队列和内存异常的严重警报。',
|
|
218
|
+
'notifications.smsTitle': '短信紧急警报',
|
|
219
|
+
'notifications.smsDescription': '可能产生标准资费。仅在需要管理员手动干预的紧急事件时使用。',
|
|
220
|
+
'notifications.criticalSecurity': '关键安全事件',
|
|
221
|
+
'notifications.criticalSecurityDesc': '在发生 Root 密码重置或双重身份验证绕过时发送短信警报。',
|
|
222
|
+
'api.generateTitle': '创建新 API 密钥',
|
|
223
|
+
'api.generateDesc': '配置权限范围并生成访问令牌。',
|
|
224
|
+
'api.keyName': '密钥名称',
|
|
225
|
+
'api.permissions': '权限范围',
|
|
226
|
+
'api.generateButton': '生成令牌',
|
|
227
|
+
'api.activeKeysTitle': '活跃 API 密钥',
|
|
228
|
+
'api.activeKeysDesc': '请妥善保管这些密钥。切勿在公共仓库中分享。',
|
|
229
|
+
'api.successTitle': 'API 密钥已生成',
|
|
230
|
+
'api.successHint': '请立即复制此令牌。关闭此提示后,您将无法再次查看它。',
|
|
231
|
+
'api.noKeys': '尚未生成活跃的 API 密钥。',
|
|
232
|
+
'error.pageNotFoundDescription': '抱歉,您访问的页面不存在或已被移除。',
|
|
233
|
+
'error.internalServerErrorDescription': '系统发生了一些问题,请稍后再试或联系支持人员。',
|
|
165
234
|
'settings.system': '系统',
|
|
166
235
|
'settings.themeMode': '主题模式',
|
|
167
236
|
'settings.light': '浅色',
|
|
@@ -316,6 +385,127 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
316
385
|
'task.status.dead_lettered': '死信',
|
|
317
386
|
'task.status.retry_scheduled': '等待重试',
|
|
318
387
|
'task.status.leased': '已领取',
|
|
388
|
+
// Public Profile
|
|
389
|
+
'publicProfile.title': '公开资料',
|
|
390
|
+
'publicProfile.projects': '项目',
|
|
391
|
+
'publicProfile.activity': '动态',
|
|
392
|
+
'publicProfile.teams': '团队',
|
|
393
|
+
'publicProfile.profile': '资料',
|
|
394
|
+
'publicProfile.viewProfile': '查看资料',
|
|
395
|
+
'publicProfile.followers': '关注者',
|
|
396
|
+
'publicProfile.following': '关注中',
|
|
397
|
+
'publicProfile.editProfile': '编辑资料',
|
|
398
|
+
'publicProfile.sendMessage': '发消息',
|
|
399
|
+
'publicProfile.addFriend': '添加好友',
|
|
400
|
+
'publicProfile.projects2Col': '项目 (两列)',
|
|
401
|
+
'publicProfile.projects3Col': '项目 (三列)',
|
|
402
|
+
'publicProfile.noProjects': '暂无项目',
|
|
403
|
+
'publicProfile.noActivity': '暂无动态',
|
|
404
|
+
'publicProfile.noTeams': '暂无团队',
|
|
405
|
+
'publicProfile.joinedDate': '加入于 {date}',
|
|
406
|
+
'publicProfile.projectMembers': '{count} 名成员',
|
|
407
|
+
'publicProfile.projectTasks': '{count} 个任务',
|
|
408
|
+
'publicProfile.activityPosted': '发布了内容',
|
|
409
|
+
'publicProfile.activityCommented': '评论了',
|
|
410
|
+
'publicProfile.activityJoined': '加入了',
|
|
411
|
+
'publicProfile.activityCreated': '创建了',
|
|
412
|
+
// Profile variants
|
|
413
|
+
'profile.company': '公司资料',
|
|
414
|
+
'profile.companyDescription': '公司简介和统计信息',
|
|
415
|
+
'profile.gamer': '玩家资料',
|
|
416
|
+
'profile.gamerDescription': '游戏成就和统计',
|
|
417
|
+
'profile.default': '个人资料',
|
|
418
|
+
'profile.defaultDescription': '个人简介和活动',
|
|
419
|
+
'profile.companyName': '公司名称',
|
|
420
|
+
'profile.industry': '行业',
|
|
421
|
+
'profile.employees': '员工数',
|
|
422
|
+
'profile.founded': '成立时间',
|
|
423
|
+
'profile.website': '网站',
|
|
424
|
+
'profile.gamerTag': '玩家标签',
|
|
425
|
+
'profile.level': '等级',
|
|
426
|
+
'profile.rank': '排名',
|
|
427
|
+
'profile.gamesPlayed': '游戏场次',
|
|
428
|
+
'profile.winRate': '胜率',
|
|
429
|
+
// Account extensions
|
|
430
|
+
'account.getStarted': '入门指南',
|
|
431
|
+
'account.getStartedDescription': '按照步骤完成初始设置,快速上手平台功能。',
|
|
432
|
+
'account.companyProfile': '公司信息',
|
|
433
|
+
'account.companyProfileDescription': '管理公司基本信息和品牌设置。',
|
|
434
|
+
'account.settingsPlain': '基本设置',
|
|
435
|
+
'account.settingsPlainDescription': '管理基本账户设置。',
|
|
436
|
+
'account.settingsSidebar': '侧栏设置',
|
|
437
|
+
'account.settingsSidebarDescription': '配置侧边栏布局和行为。',
|
|
438
|
+
'account.settingsEnterprise': '企业设置',
|
|
439
|
+
'account.settingsEnterpriseDescription': '管理企业级配置和策略。',
|
|
440
|
+
'account.importMembers': '导入成员',
|
|
441
|
+
'account.importMembersDescription': '从 CSV 文件批量导入团队成员。',
|
|
442
|
+
'account.membersStarter': '成员概览',
|
|
443
|
+
'account.membersStarterDescription': '查看和管理工作区成员。',
|
|
444
|
+
'account.securityLog': '安全日志',
|
|
445
|
+
'account.securityLogDescription': '查看账户安全相关的操作记录。',
|
|
446
|
+
'account.step': '第 {current} 步(共 {total} 步)',
|
|
447
|
+
'account.completeSetup': '完成设置',
|
|
448
|
+
'account.uploadAvatar': '上传头像',
|
|
449
|
+
'account.connectProvider': '连接服务',
|
|
450
|
+
'account.setupProfile': '完善资料',
|
|
451
|
+
'account.setupTeam': '邀请团队',
|
|
452
|
+
'account.skipSetup': '跳过设置',
|
|
453
|
+
'account.nextStep': '下一步',
|
|
454
|
+
'account.prevStep': '上一步',
|
|
455
|
+
'account.finishSetup': '完成设置',
|
|
456
|
+
'account.dragOrClick': '拖拽文件或点击上传',
|
|
457
|
+
'account.supportedFormats': '支持 CSV、XLSX 格式',
|
|
458
|
+
'account.downloadTemplate': '下载模板',
|
|
459
|
+
'account.startImport': '开始导入',
|
|
460
|
+
'account.importProgress': '导入进度',
|
|
461
|
+
'account.importComplete': '导入完成',
|
|
462
|
+
'account.imported': '已导入 {count} 条记录',
|
|
463
|
+
'account.member': '成员',
|
|
464
|
+
'account.role': '角色',
|
|
465
|
+
'account.status': '状态',
|
|
466
|
+
'account.joined': '加入时间',
|
|
467
|
+
'account.active': '活跃',
|
|
468
|
+
'account.invited': '已邀请',
|
|
469
|
+
'account.inactive': '未激活',
|
|
470
|
+
'account.inviteMember': '邀请成员',
|
|
471
|
+
'account.removeMember': '移除成员',
|
|
472
|
+
// Network
|
|
473
|
+
'network.userCards': '用户卡片',
|
|
474
|
+
'network.userCardsDescription': '以卡片形式浏览用户列表。',
|
|
475
|
+
'network.teamCrew': '团队花名册',
|
|
476
|
+
'network.teamCrewDescription': '以表格形式查看团队成员信息。',
|
|
477
|
+
'network.connect': '关注',
|
|
478
|
+
'network.connected': '已关注',
|
|
479
|
+
'network.nftCollection': 'NFT 收藏',
|
|
480
|
+
'network.nftCount': '{count} 件藏品',
|
|
481
|
+
'network.floorPrice': '地板价',
|
|
482
|
+
'network.volume': '交易量',
|
|
483
|
+
'network.viewCollection': '查看收藏',
|
|
484
|
+
// Auth
|
|
485
|
+
'auth.twoFactorSetup': '设置双重验证',
|
|
486
|
+
'auth.twoFactorVerify': '验证身份',
|
|
487
|
+
'auth.twoFactorRecovery': '恢复代码',
|
|
488
|
+
'auth.twoFactorScanQR': '使用验证器应用扫描二维码',
|
|
489
|
+
'auth.twoFactorEnterCode': '输入 6 位验证码',
|
|
490
|
+
'auth.twoFactorVerifySuccess': '双重验证已成功启用',
|
|
491
|
+
'auth.twoFactorSaveRecovery': '请妥善保存以下恢复代码',
|
|
492
|
+
'auth.twoFactorRecoveryHint': '当您无法使用验证器时,可使用恢复代码登录。每个代码只能使用一次。',
|
|
493
|
+
'auth.twoFactorDisable': '关闭双重验证',
|
|
494
|
+
'auth.twoFactorDisableConfirm': '确认要关闭双重验证吗?这将降低账户安全性。',
|
|
495
|
+
// Security Log
|
|
496
|
+
'securityLog.event': '事件',
|
|
497
|
+
'securityLog.ip': 'IP 地址',
|
|
498
|
+
'securityLog.device': '设备',
|
|
499
|
+
'securityLog.location': '位置',
|
|
500
|
+
'securityLog.time': '时间',
|
|
501
|
+
'securityLog.loginSuccess': '登录成功',
|
|
502
|
+
'securityLog.loginFailed': '登录失败',
|
|
503
|
+
'securityLog.passwordChanged': '密码已修改',
|
|
504
|
+
'securityLog.twoFactorEnabled': '已启用双重验证',
|
|
505
|
+
'securityLog.twoFactorDisabled': '已关闭双重验证',
|
|
506
|
+
'securityLog.apiKeyCreated': 'API 密钥已创建',
|
|
507
|
+
'securityLog.apiKeyRevoked': 'API 密钥已吊销',
|
|
508
|
+
'securityLog.sessionRevoked': '会话已注销',
|
|
319
509
|
},
|
|
320
510
|
'en': {
|
|
321
511
|
'common.list': 'List',
|
|
@@ -458,6 +648,19 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
458
648
|
'devtools.title': 'DevTools (Ctrl+Shift+D)',
|
|
459
649
|
// Validation
|
|
460
650
|
// Chat
|
|
651
|
+
'chat.title': 'AI Assistant',
|
|
652
|
+
'chat.typing': 'Typing...',
|
|
653
|
+
'chat.clear': 'Clear',
|
|
654
|
+
'chat.welcome': 'How can I help?',
|
|
655
|
+
'chat.welcomeDesc': 'Ask me anything about your admin panel, resources, and inventory operations.',
|
|
656
|
+
'chat.thinking': 'Thinking...',
|
|
657
|
+
'chat.placeholder': 'Type a message...',
|
|
658
|
+
'chat.shortcutHint': 'to toggle',
|
|
659
|
+
'chat.error': 'Sorry, something went wrong. Please try again.',
|
|
660
|
+
'chat.approvalRequired': 'Approval required',
|
|
661
|
+
'chat.suggestion1': 'How do I create a new resource?',
|
|
662
|
+
'chat.suggestion2': 'Explain the data model',
|
|
663
|
+
'chat.suggestion3': 'Help me write a filter query',
|
|
461
664
|
'chat.contextInfo': 'Current context: {resource}',
|
|
462
665
|
'chat.actionButton': 'Execute',
|
|
463
666
|
'chat.historyRestored': 'Chat history restored',
|
|
@@ -476,6 +679,62 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
476
679
|
'settings.appearance': 'Appearance',
|
|
477
680
|
'settings.about': 'About',
|
|
478
681
|
'settings.general': 'General',
|
|
682
|
+
'settings.security': 'Security',
|
|
683
|
+
'settings.integrations': 'Integrations',
|
|
684
|
+
'settings.notifications': 'Notifications',
|
|
685
|
+
'settings.api': 'API Settings',
|
|
686
|
+
'settings.developer': 'Developer',
|
|
687
|
+
'settings.workspace': 'Workspace',
|
|
688
|
+
'settings.securityDescription': 'Manage your password, active sessions, and multi-factor authentication preferences.',
|
|
689
|
+
'settings.integrationsDescription': 'Connect and manage third-party service extensions for your workspace.',
|
|
690
|
+
'settings.notificationsDescription': 'Configure how and when you receive system alerts.',
|
|
691
|
+
'settings.apiDescription': 'Generate and manage secure API keys to integrate external programmatic scripts.',
|
|
692
|
+
'security.twoFactorAuth': 'Two-Factor Authentication (2FA)',
|
|
693
|
+
'security.twoFactorDescription': 'Add an extra layer of security to your account by requiring a verification code in addition to your password.',
|
|
694
|
+
'security.enable2fa': 'Enable 2FA Verification',
|
|
695
|
+
'security.twoFactorActive': '2FA is currently active. Verification codes will be required on new logins.',
|
|
696
|
+
'security.twoFactorInactive': '2FA is currently disabled. Protect your account by turning it on.',
|
|
697
|
+
'security.passwordLastChanged': 'Last changed: {time}',
|
|
698
|
+
'security.activeSessions': 'Active Sessions',
|
|
699
|
+
'security.sessionsDescription': 'These are the devices that have logged into your account. Revoke any you do not recognize.',
|
|
700
|
+
'security.revokeOthers': 'Sign Out Other Devices',
|
|
701
|
+
'security.currentSession': 'Current Device',
|
|
702
|
+
'security.revokeSession': 'Sign out device',
|
|
703
|
+
'security.noSessions': 'No active sessions.',
|
|
704
|
+
'integrations.statusConnected': 'Connected',
|
|
705
|
+
'integrations.statusDisconnected': 'Disconnected',
|
|
706
|
+
'integrations.enabled': 'Enabled',
|
|
707
|
+
'integrations.disabled': 'Disabled',
|
|
708
|
+
'integrations.addMoreHint': 'Need custom integrations? Contact your administrator to register new application manifests.',
|
|
709
|
+
'notifications.emailTitle': 'Email Notifications',
|
|
710
|
+
'notifications.emailDescription': 'Get automated system messages delivered directly to your mailbox.',
|
|
711
|
+
'notifications.securityAlerts': 'Security Alerts',
|
|
712
|
+
'notifications.securityAlertsDesc': 'Get notified of password changes, login attempts, and key generations.',
|
|
713
|
+
'notifications.activityLogs': 'Activity Reports',
|
|
714
|
+
'notifications.activityLogsDesc': 'Weekly summaries of system audits, modifications, and background tasks.',
|
|
715
|
+
'notifications.systemAlerts': 'System Reports',
|
|
716
|
+
'notifications.systemAlertsDesc': 'Real-time alerts when server instances or data pipelines fail.',
|
|
717
|
+
'notifications.pushTitle': 'In-App & Push Notifications',
|
|
718
|
+
'notifications.pushDescription': 'Receive push notifications on your active browser tab.',
|
|
719
|
+
'notifications.securityPushDesc': 'Immediate browser alerts on unauthorized login detection.',
|
|
720
|
+
'notifications.activityPushDesc': 'Instant notifications on CRUD logs when colleagues edit resources.',
|
|
721
|
+
'notifications.systemPushDesc': 'Critical alerts for task queues and memory anomalies.',
|
|
722
|
+
'notifications.smsTitle': 'SMS Emergency Alerts',
|
|
723
|
+
'notifications.smsDescription': 'Standard rates apply. Used only for urgent events requiring manual administrator intervention.',
|
|
724
|
+
'notifications.criticalSecurity': 'Critical Security Events',
|
|
725
|
+
'notifications.criticalSecurityDesc': 'SMS alert sent when root password resets or 2FA bypass occurs.',
|
|
726
|
+
'api.generateTitle': 'Create New API Key',
|
|
727
|
+
'api.generateDesc': 'Configure scopes and generate a token.',
|
|
728
|
+
'api.keyName': 'Key Name',
|
|
729
|
+
'api.permissions': 'Permissions',
|
|
730
|
+
'api.generateButton': 'Generate Token',
|
|
731
|
+
'api.activeKeysTitle': 'Active API Keys',
|
|
732
|
+
'api.activeKeysDesc': 'Keep these keys secure. Do not share them in public repositories.',
|
|
733
|
+
'api.successTitle': 'API Key Generated',
|
|
734
|
+
'api.successHint': 'Copy this token now. It will not be shown again.',
|
|
735
|
+
'api.noKeys': 'No active API keys generated yet.',
|
|
736
|
+
'error.pageNotFoundDescription': 'Sorry, the page you are looking for does not exist or has been moved.',
|
|
737
|
+
'error.internalServerErrorDescription': 'Something went wrong on our end. Please try again later or contact support.',
|
|
479
738
|
'settings.system': 'System',
|
|
480
739
|
'settings.themeMode': 'Theme Mode',
|
|
481
740
|
'settings.light': 'Light',
|
|
@@ -630,6 +889,127 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
630
889
|
'task.status.dead_lettered': 'Dead lettered',
|
|
631
890
|
'task.status.retry_scheduled': 'Retry scheduled',
|
|
632
891
|
'task.status.leased': 'Leased',
|
|
892
|
+
// Public Profile
|
|
893
|
+
'publicProfile.title': 'Public Profile',
|
|
894
|
+
'publicProfile.projects': 'Projects',
|
|
895
|
+
'publicProfile.activity': 'Activity',
|
|
896
|
+
'publicProfile.teams': 'Teams',
|
|
897
|
+
'publicProfile.profile': 'Profile',
|
|
898
|
+
'publicProfile.viewProfile': 'View Profile',
|
|
899
|
+
'publicProfile.followers': 'Followers',
|
|
900
|
+
'publicProfile.following': 'Following',
|
|
901
|
+
'publicProfile.editProfile': 'Edit Profile',
|
|
902
|
+
'publicProfile.sendMessage': 'Send Message',
|
|
903
|
+
'publicProfile.addFriend': 'Add Friend',
|
|
904
|
+
'publicProfile.projects2Col': 'Projects (2 Columns)',
|
|
905
|
+
'publicProfile.projects3Col': 'Projects (3 Columns)',
|
|
906
|
+
'publicProfile.noProjects': 'No projects yet',
|
|
907
|
+
'publicProfile.noActivity': 'No activity yet',
|
|
908
|
+
'publicProfile.noTeams': 'No teams yet',
|
|
909
|
+
'publicProfile.joinedDate': 'Joined {date}',
|
|
910
|
+
'publicProfile.projectMembers': '{count} members',
|
|
911
|
+
'publicProfile.projectTasks': '{count} tasks',
|
|
912
|
+
'publicProfile.activityPosted': 'posted',
|
|
913
|
+
'publicProfile.activityCommented': 'commented on',
|
|
914
|
+
'publicProfile.activityJoined': 'joined',
|
|
915
|
+
'publicProfile.activityCreated': 'created',
|
|
916
|
+
// Profile variants
|
|
917
|
+
'profile.company': 'Company Profile',
|
|
918
|
+
'profile.companyDescription': 'Company overview and statistics',
|
|
919
|
+
'profile.gamer': 'Gamer Profile',
|
|
920
|
+
'profile.gamerDescription': 'Gaming achievements and stats',
|
|
921
|
+
'profile.default': 'Default Profile',
|
|
922
|
+
'profile.defaultDescription': 'Personal overview and activity',
|
|
923
|
+
'profile.companyName': 'Company Name',
|
|
924
|
+
'profile.industry': 'Industry',
|
|
925
|
+
'profile.employees': 'Employees',
|
|
926
|
+
'profile.founded': 'Founded',
|
|
927
|
+
'profile.website': 'Website',
|
|
928
|
+
'profile.gamerTag': 'Gamer Tag',
|
|
929
|
+
'profile.level': 'Level',
|
|
930
|
+
'profile.rank': 'Rank',
|
|
931
|
+
'profile.gamesPlayed': 'Games Played',
|
|
932
|
+
'profile.winRate': 'Win Rate',
|
|
933
|
+
// Account extensions
|
|
934
|
+
'account.getStarted': 'Get Started',
|
|
935
|
+
'account.getStartedDescription': 'Follow the steps to complete your initial setup and get up to speed quickly.',
|
|
936
|
+
'account.companyProfile': 'Company Profile',
|
|
937
|
+
'account.companyProfileDescription': 'Manage company information and branding.',
|
|
938
|
+
'account.settingsPlain': 'Basic Settings',
|
|
939
|
+
'account.settingsPlainDescription': 'Manage basic account settings.',
|
|
940
|
+
'account.settingsSidebar': 'Sidebar Settings',
|
|
941
|
+
'account.settingsSidebarDescription': 'Configure sidebar layout and behavior.',
|
|
942
|
+
'account.settingsEnterprise': 'Enterprise Settings',
|
|
943
|
+
'account.settingsEnterpriseDescription': 'Manage enterprise-level configuration and policies.',
|
|
944
|
+
'account.importMembers': 'Import Members',
|
|
945
|
+
'account.importMembersDescription': 'Bulk import team members from a CSV file.',
|
|
946
|
+
'account.membersStarter': 'Members Overview',
|
|
947
|
+
'account.membersStarterDescription': 'View and manage workspace members.',
|
|
948
|
+
'account.securityLog': 'Security Log',
|
|
949
|
+
'account.securityLogDescription': 'View security-related activity logs for your account.',
|
|
950
|
+
'account.step': 'Step {current} of {total}',
|
|
951
|
+
'account.completeSetup': 'Complete Setup',
|
|
952
|
+
'account.uploadAvatar': 'Upload Avatar',
|
|
953
|
+
'account.connectProvider': 'Connect Provider',
|
|
954
|
+
'account.setupProfile': 'Set Up Profile',
|
|
955
|
+
'account.setupTeam': 'Invite Team',
|
|
956
|
+
'account.skipSetup': 'Skip Setup',
|
|
957
|
+
'account.nextStep': 'Next Step',
|
|
958
|
+
'account.prevStep': 'Previous Step',
|
|
959
|
+
'account.finishSetup': 'Finish Setup',
|
|
960
|
+
'account.dragOrClick': 'Drag file or click to upload',
|
|
961
|
+
'account.supportedFormats': 'Supports CSV, XLSX formats',
|
|
962
|
+
'account.downloadTemplate': 'Download Template',
|
|
963
|
+
'account.startImport': 'Start Import',
|
|
964
|
+
'account.importProgress': 'Import Progress',
|
|
965
|
+
'account.importComplete': 'Import Complete',
|
|
966
|
+
'account.imported': 'Imported {count} records',
|
|
967
|
+
'account.member': 'Member',
|
|
968
|
+
'account.role': 'Role',
|
|
969
|
+
'account.status': 'Status',
|
|
970
|
+
'account.joined': 'Joined',
|
|
971
|
+
'account.active': 'Active',
|
|
972
|
+
'account.invited': 'Invited',
|
|
973
|
+
'account.inactive': 'Inactive',
|
|
974
|
+
'account.inviteMember': 'Invite Member',
|
|
975
|
+
'account.removeMember': 'Remove Member',
|
|
976
|
+
// Network
|
|
977
|
+
'network.userCards': 'User Cards',
|
|
978
|
+
'network.userCardsDescription': 'Browse users in card layout.',
|
|
979
|
+
'network.teamCrew': 'Team Crew',
|
|
980
|
+
'network.teamCrewDescription': 'View team members in table layout.',
|
|
981
|
+
'network.connect': 'Follow',
|
|
982
|
+
'network.connected': 'Following',
|
|
983
|
+
'network.nftCollection': 'NFT Collection',
|
|
984
|
+
'network.nftCount': '{count} items',
|
|
985
|
+
'network.floorPrice': 'Floor Price',
|
|
986
|
+
'network.volume': 'Volume',
|
|
987
|
+
'network.viewCollection': 'View Collection',
|
|
988
|
+
// Auth
|
|
989
|
+
'auth.twoFactorSetup': 'Set Up Two-Factor Auth',
|
|
990
|
+
'auth.twoFactorVerify': 'Verify Identity',
|
|
991
|
+
'auth.twoFactorRecovery': 'Recovery Codes',
|
|
992
|
+
'auth.twoFactorScanQR': 'Scan the QR code with your authenticator app',
|
|
993
|
+
'auth.twoFactorEnterCode': 'Enter 6-digit verification code',
|
|
994
|
+
'auth.twoFactorVerifySuccess': 'Two-factor authentication enabled successfully',
|
|
995
|
+
'auth.twoFactorSaveRecovery': 'Save these recovery codes in a safe place',
|
|
996
|
+
'auth.twoFactorRecoveryHint': 'You can use recovery codes to log in when you cannot access your authenticator. Each code can only be used once.',
|
|
997
|
+
'auth.twoFactorDisable': 'Disable Two-Factor Auth',
|
|
998
|
+
'auth.twoFactorDisableConfirm': 'Are you sure you want to disable two-factor authentication? This will reduce your account security.',
|
|
999
|
+
// Security Log
|
|
1000
|
+
'securityLog.event': 'Event',
|
|
1001
|
+
'securityLog.ip': 'IP Address',
|
|
1002
|
+
'securityLog.device': 'Device',
|
|
1003
|
+
'securityLog.location': 'Location',
|
|
1004
|
+
'securityLog.time': 'Time',
|
|
1005
|
+
'securityLog.loginSuccess': 'Login Successful',
|
|
1006
|
+
'securityLog.loginFailed': 'Login Failed',
|
|
1007
|
+
'securityLog.passwordChanged': 'Password Changed',
|
|
1008
|
+
'securityLog.twoFactorEnabled': '2FA Enabled',
|
|
1009
|
+
'securityLog.twoFactorDisabled': '2FA Disabled',
|
|
1010
|
+
'securityLog.apiKeyCreated': 'API Key Created',
|
|
1011
|
+
'securityLog.apiKeyRevoked': 'API Key Revoked',
|
|
1012
|
+
'securityLog.sessionRevoked': 'Session Revoked',
|
|
633
1013
|
},
|
|
634
1014
|
};
|
|
635
1015
|
|
package/src/live-sse.ts
CHANGED
|
@@ -123,7 +123,7 @@ export function createSSELiveProvider(options: SSELiveProviderOptions): LiveProv
|
|
|
123
123
|
subscribers.set(resource, new Set());
|
|
124
124
|
isNewResource = true;
|
|
125
125
|
}
|
|
126
|
-
subscribers.get(resource)
|
|
126
|
+
subscribers.get(resource)?.add(callback);
|
|
127
127
|
|
|
128
128
|
if (!eventSource && typeof EventSource !== 'undefined') {
|
|
129
129
|
connect();
|
package/src/live-websocket.ts
CHANGED
|
@@ -121,7 +121,7 @@ export function createWebSocketLiveProvider(options: WebSocketLiveProviderOption
|
|
|
121
121
|
if (!subscribers.has(resource)) {
|
|
122
122
|
subscribers.set(resource, new Set());
|
|
123
123
|
}
|
|
124
|
-
subscribers.get(resource)
|
|
124
|
+
subscribers.get(resource)?.add(callback);
|
|
125
125
|
lastParams.set(resource, liveParams);
|
|
126
126
|
|
|
127
127
|
// Ensure connection is open
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1
2
|
import { createMutation, useQueryClient } from '@tanstack/svelte-query';
|
|
2
3
|
import { getAdminOptions } from './options.svelte';
|
|
3
4
|
import { getDataProviderForResource, getResource, getLiveProvider } from './context.svelte';
|
|
4
5
|
import { useParsed } from './useParsed.svelte';
|
|
5
6
|
import { audit } from './audit';
|
|
6
7
|
import { UndoError } from './types';
|
|
7
|
-
import type { BaseRecord, HttpError,
|
|
8
|
+
import type { BaseRecord, HttpError, KnownResources } from './types';
|
|
8
9
|
import { checkError, createOvertimeTracker, fireSuccessNotification, fireErrorNotification } from './hook-utils.svelte';
|
|
9
10
|
import type { NotificationConfig, OvertimeOptions } from './hook-utils.svelte';
|
|
10
11
|
import { toast } from './toast.svelte';
|
|
@@ -132,14 +133,14 @@ export function useCreate<TData extends BaseRecord = BaseRecord, TError = HttpEr
|
|
|
132
133
|
// Invalidation in onSuccess for create (refine pattern — no optimistic data to reconcile on error)
|
|
133
134
|
invalidateByScopes(queryClient, resName, params.invalidates, ['list', 'many'], undefined, params.dataProviderName);
|
|
134
135
|
if (typeof options.mutationOptions?.onSuccess === 'function') {
|
|
135
|
-
(options.mutationOptions.onSuccess as
|
|
136
|
+
(options.mutationOptions.onSuccess as (...a: unknown[]) => unknown)(data, params, context);
|
|
136
137
|
}
|
|
137
138
|
},
|
|
138
139
|
onError: (error, params, context) => {
|
|
139
140
|
checkError(error);
|
|
140
141
|
fireErrorNotification(params.errorNotification, 'Create failed', error, params.resource ?? defaultResource);
|
|
141
142
|
if (typeof options.mutationOptions?.onError === 'function') {
|
|
142
|
-
(options.mutationOptions.onError as
|
|
143
|
+
(options.mutationOptions.onError as (...a: unknown[]) => unknown)(error, params, context);
|
|
143
144
|
}
|
|
144
145
|
},
|
|
145
146
|
}));
|
|
@@ -242,7 +243,7 @@ export function useUpdate<TData extends BaseRecord = BaseRecord, TError = HttpEr
|
|
|
242
243
|
|
|
243
244
|
if (updater.list !== false) {
|
|
244
245
|
queryClient.setQueriesData({ predicate: (q) => dp(q) && q.queryKey[1] === resName && q.queryKey[2] === 'list' }, (old: unknown) => {
|
|
245
|
-
if (typeof updater.list === 'function') return updater.list
|
|
246
|
+
if (typeof updater.list === 'function') return updater.list(old, params.variables, targetId);
|
|
246
247
|
if (!old || typeof old !== 'object' || !('data' in old)) return old;
|
|
247
248
|
const o = old as { data: Record<string, unknown>[] };
|
|
248
249
|
return { ...o, data: o.data.map((item) => String(item[pk]) === String(targetId) ? deepMerge(item, params.variables) : item) };
|
|
@@ -251,7 +252,7 @@ export function useUpdate<TData extends BaseRecord = BaseRecord, TError = HttpEr
|
|
|
251
252
|
|
|
252
253
|
if (updater.many !== false) {
|
|
253
254
|
queryClient.setQueriesData({ predicate: (q) => dp(q) && q.queryKey[1] === resName && q.queryKey[2] === 'many' }, (old: unknown) => {
|
|
254
|
-
if (typeof updater.many === 'function') return updater.many
|
|
255
|
+
if (typeof updater.many === 'function') return updater.many(old, params.variables, targetId);
|
|
255
256
|
if (!old || typeof old !== 'object' || !('data' in old)) return old;
|
|
256
257
|
const o = old as { data: Record<string, unknown>[] };
|
|
257
258
|
return { ...o, data: o.data.map((item) => String(item[pk]) === String(targetId) ? deepMerge(item, params.variables) : item) };
|
|
@@ -268,7 +269,7 @@ export function useUpdate<TData extends BaseRecord = BaseRecord, TError = HttpEr
|
|
|
268
269
|
audit({ action: 'update', resource: resName, recordId: String(targetId) });
|
|
269
270
|
publishLiveEvent(resName, 'updated', targetId != null ? [targetId] : undefined);
|
|
270
271
|
if (typeof options.mutationOptions?.onSuccess === 'function') {
|
|
271
|
-
(options.mutationOptions.onSuccess as
|
|
272
|
+
(options.mutationOptions.onSuccess as (...a: unknown[]) => unknown)(data, params, extractedCtx);
|
|
272
273
|
}
|
|
273
274
|
},
|
|
274
275
|
onSettled: (_data, error, params) => {
|
|
@@ -277,7 +278,7 @@ export function useUpdate<TData extends BaseRecord = BaseRecord, TError = HttpEr
|
|
|
277
278
|
const targetId = params.id ?? defaultId;
|
|
278
279
|
invalidateByScopes(queryClient, resName, params.invalidates, ['list', 'many', 'detail'], targetId != null ? targetId : undefined, params.dataProviderName);
|
|
279
280
|
if (typeof options.mutationOptions?.onSettled === 'function') {
|
|
280
|
-
(options.mutationOptions.onSettled as
|
|
281
|
+
(options.mutationOptions.onSettled as (...a: unknown[]) => unknown)(_data, error, params, undefined);
|
|
281
282
|
}
|
|
282
283
|
},
|
|
283
284
|
onError: (error, params, context: unknown) => {
|
|
@@ -292,7 +293,7 @@ export function useUpdate<TData extends BaseRecord = BaseRecord, TError = HttpEr
|
|
|
292
293
|
const extractedCtx = ctx?._svadmin_ctx ? ctx.userContext : context;
|
|
293
294
|
fireErrorNotification(params.errorNotification, 'Update failed', error, params.resource ?? defaultResource);
|
|
294
295
|
if (typeof options.mutationOptions?.onError === 'function') {
|
|
295
|
-
(options.mutationOptions.onError as
|
|
296
|
+
(options.mutationOptions.onError as (...a: unknown[]) => unknown)(error, params, extractedCtx);
|
|
296
297
|
}
|
|
297
298
|
},
|
|
298
299
|
}));
|
|
@@ -409,7 +410,7 @@ export function useDelete<TData extends BaseRecord = BaseRecord, TError = HttpEr
|
|
|
409
410
|
audit({ action: 'delete', resource: resName, recordId: String(targetId) });
|
|
410
411
|
publishLiveEvent(resName, 'deleted', targetId != null ? [targetId] : undefined);
|
|
411
412
|
if (typeof options.mutationOptions?.onSuccess === 'function') {
|
|
412
|
-
(options.mutationOptions.onSuccess as
|
|
413
|
+
(options.mutationOptions.onSuccess as (...a: unknown[]) => unknown)(data, params, extractedCtx);
|
|
413
414
|
}
|
|
414
415
|
},
|
|
415
416
|
onSettled: (_data, error, params) => {
|
|
@@ -418,7 +419,7 @@ export function useDelete<TData extends BaseRecord = BaseRecord, TError = HttpEr
|
|
|
418
419
|
const targetId = params.id ?? defaultId;
|
|
419
420
|
invalidateByScopes(queryClient, resName, params.invalidates, ['list', 'many'], targetId != null ? targetId : undefined, params.dataProviderName);
|
|
420
421
|
if (typeof options.mutationOptions?.onSettled === 'function') {
|
|
421
|
-
(options.mutationOptions.onSettled as
|
|
422
|
+
(options.mutationOptions.onSettled as (...a: unknown[]) => unknown)(_data, error, params, undefined);
|
|
422
423
|
}
|
|
423
424
|
},
|
|
424
425
|
onError: (error, params, context: unknown) => {
|
|
@@ -433,7 +434,7 @@ export function useDelete<TData extends BaseRecord = BaseRecord, TError = HttpEr
|
|
|
433
434
|
const extractedCtx = ctx?._svadmin_ctx ? ctx.userContext : context;
|
|
434
435
|
fireErrorNotification(params.errorNotification, 'Delete failed', error, params.resource ?? defaultResource);
|
|
435
436
|
if (typeof options.mutationOptions?.onError === 'function') {
|
|
436
|
-
(options.mutationOptions.onError as
|
|
437
|
+
(options.mutationOptions.onError as (...a: unknown[]) => unknown)(error, params, extractedCtx);
|
|
437
438
|
}
|
|
438
439
|
},
|
|
439
440
|
}));
|
|
@@ -93,7 +93,7 @@ export async function canAccessAsync(resourceOrBatch: string | CanParams[], acti
|
|
|
93
93
|
return provider.can(resourceOrBatch);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
return provider.can({ resource: resourceOrBatch, action: action
|
|
96
|
+
return provider.can({ resource: resourceOrBatch, action: action as string, params }) as Promise<CanResult>;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
// ─── Feature Gate ─────────────────────────────────────────────
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createQuery
|
|
1
|
+
import { createQuery } from '@tanstack/svelte-query';
|
|
2
2
|
import { getAdminOptions } from './options.svelte';
|
|
3
3
|
import { getDataProviderForResource, getDataProvider, getLiveProvider } from './context.svelte';
|
|
4
4
|
import { useParsed } from './useParsed.svelte';
|
|
@@ -57,7 +57,7 @@ function cloneQueryKeyPart<T>(value: T): T {
|
|
|
57
57
|
return value;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
export interface UseListOptions<
|
|
60
|
+
export interface UseListOptions<_TData extends BaseRecord = BaseRecord, _TError = HttpError> {
|
|
61
61
|
resource?: KnownResources;
|
|
62
62
|
pagination?: Pagination;
|
|
63
63
|
sorters?: Sort[];
|
|
@@ -146,7 +146,7 @@ export function useList<TData extends BaseRecord = BaseRecord, TError = HttpErro
|
|
|
146
146
|
|
|
147
147
|
// ─── useOne ────────────────────────────────────────────────────
|
|
148
148
|
|
|
149
|
-
export interface UseOneOptions<
|
|
149
|
+
export interface UseOneOptions<_TData extends BaseRecord = BaseRecord, _TError = HttpError> {
|
|
150
150
|
resource?: KnownResources;
|
|
151
151
|
id?: string | number;
|
|
152
152
|
meta?: Record<string, unknown>;
|
|
@@ -256,7 +256,7 @@ export function useShow<TData extends BaseRecord = BaseRecord, TError = HttpErro
|
|
|
256
256
|
|
|
257
257
|
// ─── useMany ───────────────────────────────────────────────────
|
|
258
258
|
|
|
259
|
-
export interface UseManyOptions<
|
|
259
|
+
export interface UseManyOptions<_TData extends BaseRecord = BaseRecord, _TError = HttpError> {
|
|
260
260
|
resource: KnownResources;
|
|
261
261
|
ids: (string | number)[];
|
|
262
262
|
meta?: Record<string, unknown>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable svelte/prefer-svelte-reactivity */
|
|
2
|
+
import { navigate } from './router';
|
|
2
3
|
import { getResource, getResources, getRouterProvider } from './context.svelte';
|
|
3
4
|
import { useParsed } from './useParsed.svelte';
|
|
4
5
|
|
|
@@ -7,7 +7,7 @@ import type { UseListOptions, MaybeGetter } from './query-hooks.svelte';
|
|
|
7
7
|
|
|
8
8
|
export type FilterSetMode = 'merge' | 'replace';
|
|
9
9
|
|
|
10
|
-
export interface UseTableOptions<TQueryFnData extends BaseRecord = BaseRecord, TError = HttpError,
|
|
10
|
+
export interface UseTableOptions<TQueryFnData extends BaseRecord = BaseRecord, TError = HttpError, _TSearchVariables = Record<string, unknown>> extends Omit<UseListOptions<TQueryFnData, TError>, 'pagination' | 'sorters' | 'filters'> {
|
|
11
11
|
resource?: KnownResources;
|
|
12
12
|
pagination?: Pagination;
|
|
13
13
|
sorters?: {
|
|
@@ -80,7 +80,7 @@ export function useTable<
|
|
|
80
80
|
|
|
81
81
|
const tableQueryInfo = useList<TQueryFnData, TError>(() => {
|
|
82
82
|
const currentOpts = getOptions();
|
|
83
|
-
const { meta, syncWithLocation, dataProviderName, pagination: _p, sorters: _s, filters: _f, ...restOptions } = currentOpts;
|
|
83
|
+
const { meta, syncWithLocation: _swl, dataProviderName, pagination: _p, sorters: _s, filters: _f, ...restOptions } = currentOpts;
|
|
84
84
|
return {
|
|
85
85
|
resource: currentOpts.resource ?? parsed.resource ?? '',
|
|
86
86
|
pagination: queryPagination,
|
|
@@ -209,6 +209,7 @@ export function useTable<
|
|
|
209
209
|
if (vb == null && va != null) return -1;
|
|
210
210
|
if (va == null && vb == null) continue;
|
|
211
211
|
|
|
212
|
+
// eslint-disable-next-line no-useless-assignment
|
|
212
213
|
let cmp = 0;
|
|
213
214
|
if (typeof va === 'number' && typeof vb === 'number') cmp = va - vb;
|
|
214
215
|
else if (va instanceof Date && vb instanceof Date) cmp = va.getTime() - vb.getTime();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable svelte/prefer-svelte-reactivity */
|
|
1
2
|
// TableState — Svelte 5 Class State Pattern for table data management
|
|
2
3
|
// Reactive class that encapsulates all table state with fine-grained $state/$derived
|
|
3
4
|
|
|
@@ -94,7 +95,7 @@ export class TableState<TData extends BaseRecord = BaseRecord, TError = HttpErro
|
|
|
94
95
|
this.currentFilters = initFilters;
|
|
95
96
|
|
|
96
97
|
// Create query
|
|
97
|
-
const _this = this;
|
|
98
|
+
const _this = this; // eslint-disable-line @typescript-eslint/no-this-alias
|
|
98
99
|
const { meta, syncWithLocation: _s, dataProviderName, pagination: _p, sorters: _sr, filters: _f, ...restOptions } = options;
|
|
99
100
|
this._queryResult = useList<TData, TError>({
|
|
100
101
|
resource,
|
|
@@ -226,7 +227,7 @@ export class TableState<TData extends BaseRecord = BaseRecord, TError = HttpErro
|
|
|
226
227
|
get clientData(): TData[] {
|
|
227
228
|
if (this.paginationMode === 'server') return [];
|
|
228
229
|
const allData = this.data;
|
|
229
|
-
|
|
230
|
+
const sorted = [...allData];
|
|
230
231
|
const activeSorters = this.sortersMode === 'off' ? this.effectiveSorters : [];
|
|
231
232
|
if (activeSorters.length > 0) {
|
|
232
233
|
sorted.sort((a, b) => {
|
package/src/task-hooks.svelte.ts
CHANGED
|
@@ -74,7 +74,7 @@ export function useSubmitTask<
|
|
|
74
74
|
return { mutation, get overtime() { return overtime; } };
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
export interface UseTaskOptions<TTask extends TaskRecord = TaskRecord,
|
|
77
|
+
export interface UseTaskOptions<TTask extends TaskRecord = TaskRecord, _TError = HttpError> {
|
|
78
78
|
taskId?: string;
|
|
79
79
|
taskProvider?: TaskProvider<TTask>;
|
|
80
80
|
queryOptions?: {
|
|
@@ -138,7 +138,7 @@ export function useTask<TTask extends TaskRecord = TaskRecord, TError = HttpErro
|
|
|
138
138
|
}) as typeof query & { overtime: typeof overtime };
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
export interface UseTaskListOptions<TTask extends TaskRecord = TaskRecord,
|
|
141
|
+
export interface UseTaskListOptions<TTask extends TaskRecord = TaskRecord, _TError = HttpError> {
|
|
142
142
|
params?: Record<string, unknown>;
|
|
143
143
|
dlq?: boolean;
|
|
144
144
|
taskProvider?: TaskProvider<TTask>;
|
package/src/theme.svelte.ts
CHANGED
|
@@ -235,6 +235,8 @@ export interface ThemeConfig {
|
|
|
235
235
|
disableColorScheme?: boolean;
|
|
236
236
|
/** Built-in color preset name (e.g. 'indigo', 'blue') or a custom ColorPreset object */
|
|
237
237
|
colorPreset?: ColorPreset | string;
|
|
238
|
+
/** Layout style option: 'default' or 'clean-flat' for high-contrast flat panel design */
|
|
239
|
+
layoutPreset?: 'default' | 'clean-flat';
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
const STORAGE_KEY = 'svadmin-theme';
|
|
@@ -255,7 +257,7 @@ export function configureTheme(config: ThemeConfig): void {
|
|
|
255
257
|
if (preset) {
|
|
256
258
|
colorTheme = preset.name as ColorTheme;
|
|
257
259
|
if (typeof window !== 'undefined') {
|
|
258
|
-
try { localStorage.setItem(COLOR_STORAGE_KEY, preset.name); } catch {}
|
|
260
|
+
try { localStorage.setItem(COLOR_STORAGE_KEY, preset.name); } catch { /* intentional */}
|
|
259
261
|
}
|
|
260
262
|
applyColorTheme(preset.name as ColorTheme);
|
|
261
263
|
applyColorPreset(preset);
|
|
@@ -328,6 +330,13 @@ function applyTheme(m: ThemeMode): void {
|
|
|
328
330
|
|
|
329
331
|
const strategy = themeConfig.strategy ?? 'standard';
|
|
330
332
|
const el = document.documentElement;
|
|
333
|
+
|
|
334
|
+
// Apply clean-flat layout preset class to html
|
|
335
|
+
if (themeConfig.layoutPreset === 'clean-flat') {
|
|
336
|
+
el.classList.add('layout-clean-flat');
|
|
337
|
+
} else {
|
|
338
|
+
el.classList.remove('layout-clean-flat');
|
|
339
|
+
}
|
|
331
340
|
|
|
332
341
|
if (strategy === 'dark-first') {
|
|
333
342
|
// Dark-first: default is dark, add 'light' class for light mode
|
|
@@ -369,7 +378,7 @@ export function getTheme(): ThemeMode {
|
|
|
369
378
|
export function setTheme(newMode: ThemeMode): void {
|
|
370
379
|
mode = newMode;
|
|
371
380
|
if (typeof window !== 'undefined') {
|
|
372
|
-
try { localStorage.setItem(STORAGE_KEY, newMode); } catch {}
|
|
381
|
+
try { localStorage.setItem(STORAGE_KEY, newMode); } catch { /* intentional */}
|
|
373
382
|
}
|
|
374
383
|
applyTheme(newMode);
|
|
375
384
|
// Re-apply color preset — light/dark CSS variable overrides differ
|
|
@@ -416,7 +425,7 @@ export function getColorTheme(): ColorTheme {
|
|
|
416
425
|
export function setColorTheme(ct: ColorTheme): void {
|
|
417
426
|
colorTheme = ct;
|
|
418
427
|
if (typeof window !== 'undefined') {
|
|
419
|
-
try { localStorage.setItem(COLOR_STORAGE_KEY, ct); } catch {}
|
|
428
|
+
try { localStorage.setItem(COLOR_STORAGE_KEY, ct); } catch { /* intentional */}
|
|
420
429
|
}
|
|
421
430
|
applyColorTheme(ct);
|
|
422
431
|
const preset = builtinPresets[ct];
|
package/src/toast.svelte.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -256,7 +256,7 @@ export interface TaskSubscription {
|
|
|
256
256
|
export interface TaskHandle<TTask extends TaskRecord = TaskRecord> {
|
|
257
257
|
id?: string;
|
|
258
258
|
wait(): Promise<TTask>;
|
|
259
|
-
subscribe?(callback: (task: TTask) => void): TaskSubscription | (() => void) |
|
|
259
|
+
subscribe?(callback: (task: TTask) => void): TaskSubscription | (() => void) | undefined;
|
|
260
260
|
cancel?(): Promise<unknown>;
|
|
261
261
|
retry?(): Promise<unknown>;
|
|
262
262
|
}
|
|
@@ -273,7 +273,7 @@ export interface TaskProvider<TTask extends TaskRecord = TaskRecord> {
|
|
|
273
273
|
listDlq?(params?: Record<string, unknown>): Promise<TaskListResult<TTask>>;
|
|
274
274
|
cancel?(taskId: string): Promise<unknown>;
|
|
275
275
|
retry?(taskId: string): Promise<unknown>;
|
|
276
|
-
subscribe?(taskId: string, callback: (task: TTask) => void): TaskSubscription | (() => void) |
|
|
276
|
+
subscribe?(taskId: string, callback: (task: TTask) => void): TaskSubscription | (() => void) | undefined;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
// ─── AuthProvider ─────────────────────────────────────────────
|
|
@@ -448,6 +448,7 @@ export interface FieldDefinition {
|
|
|
448
448
|
* // list.data → { id: string; name: string; email: string }[]
|
|
449
449
|
* ```
|
|
450
450
|
*/
|
|
451
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
451
452
|
export interface ResourceTypeMap {}
|
|
452
453
|
|
|
453
454
|
/** When ResourceTypeMap is empty → string; otherwise → registered keys */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable svelte/prefer-svelte-reactivity */
|
|
1
2
|
// UnsavedChangesNotifier — guards against accidental navigation with dirty forms
|
|
2
3
|
// Supports: browser tab close, hash-based SPA routing, and framework router interception
|
|
3
4
|
|
|
@@ -14,7 +15,7 @@ export function resetUnsavedChanges(): void {
|
|
|
14
15
|
dirty = false;
|
|
15
16
|
_unsavedChangesCleanup?.();
|
|
16
17
|
_unsavedChangesCleanup = null;
|
|
17
|
-
|
|
18
|
+
__navigationGuard = null;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export function getUnsavedChanges(): boolean {
|
|
@@ -30,7 +31,7 @@ interface NavigationGuard {
|
|
|
30
31
|
(opts: { cancel: () => void }): void;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
let
|
|
34
|
+
let __navigationGuard: NavigationGuard | null = null;
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Register a framework-level navigation guard.
|
|
@@ -51,7 +52,7 @@ let _navigationGuard: NavigationGuard | null = null;
|
|
|
51
52
|
* and pass a `beforeNavigate` callback.
|
|
52
53
|
*/
|
|
53
54
|
export function registerNavigationGuard(guard: NavigationGuard): void {
|
|
54
|
-
|
|
55
|
+
__navigationGuard = guard;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
package/src/useParsed.svelte.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
/* eslint-disable svelte/prefer-svelte-reactivity */
|
|
1
2
|
import { useForm, type UseFormOptions, type UseFormReturn } from './form-hooks.svelte';
|
|
2
3
|
import { useQueryClient } from '@tanstack/svelte-query';
|
|
3
4
|
import { useParsed } from './useParsed.svelte';
|
|
4
5
|
import { getResource, getResources } from './context.svelte';
|
|
5
6
|
import { getAdminOptions } from './options.svelte';
|
|
6
|
-
import { navigate } from './router';
|
|
7
7
|
import { t } from './i18n.svelte';
|
|
8
8
|
import type { BaseRecord, HttpError, Filter, KnownResources, ResourceDefinition } from './types';
|
|
9
9
|
import { useList, type UseListOptions } from './query-hooks.svelte';
|
|
@@ -162,7 +162,7 @@ export function useBreadcrumb() {
|
|
|
162
162
|
|
|
163
163
|
// ─── useRelation ───────────────────────────────────────────
|
|
164
164
|
|
|
165
|
-
export interface UseRelationOptions<
|
|
165
|
+
export interface UseRelationOptions<_TData extends BaseRecord = BaseRecord, _TError = HttpError> {
|
|
166
166
|
resource: KnownResources;
|
|
167
167
|
id?: string | number;
|
|
168
168
|
pagination?: { current?: number; pageSize?: number };
|