@svadmin/core 0.37.0 → 0.39.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 +2 -2
- package/src/audit.ts +60 -2
- package/src/auth-hooks.svelte.ts +49 -16
- package/src/context.svelte.ts +39 -0
- package/src/enterprise.ts +146 -0
- package/src/hook-utils.svelte.ts +18 -3
- package/src/i18n.svelte.ts +6 -0
- package/src/index.ts +22 -1
- package/src/provider-bundle.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"author": "zuohuadong",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"@testing-library/dom": "^10.4.1",
|
|
15
15
|
"@testing-library/svelte": "^5.4.2",
|
|
16
16
|
"@testing-library/user-event": "^14.6.4",
|
|
17
|
-
"@types/bun": "^1.
|
|
17
|
+
"@types/bun": "^1.4.0",
|
|
18
18
|
"@types/jsdom": "^30.0.0",
|
|
19
19
|
"happy-dom": "^20.11.2",
|
|
20
20
|
"jsdom": "^30.0.1",
|
package/src/audit.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Full AuditLogProvider interface
|
|
3
3
|
|
|
4
4
|
export interface AuditEntry {
|
|
5
|
+
id?: string | number;
|
|
5
6
|
timestamp: string;
|
|
6
7
|
action: 'create' | 'update' | 'delete' | 'login' | 'logout';
|
|
7
8
|
resource?: string;
|
|
@@ -11,6 +12,13 @@ export interface AuditEntry {
|
|
|
11
12
|
data?: Record<string, unknown>;
|
|
12
13
|
previousData?: Record<string, unknown>;
|
|
13
14
|
meta?: Record<string, unknown>;
|
|
15
|
+
outcome?: 'success' | 'failure';
|
|
16
|
+
tenantId?: string | number;
|
|
17
|
+
requestId?: string;
|
|
18
|
+
traceId?: string;
|
|
19
|
+
ipAddress?: string;
|
|
20
|
+
userAgent?: string;
|
|
21
|
+
error?: { message: string; code?: string };
|
|
14
22
|
}
|
|
15
23
|
|
|
16
24
|
export type AuditHandler = (entry: AuditEntry) => void | Promise<void>;
|
|
@@ -22,9 +30,20 @@ export type AuditHandler = (entry: AuditEntry) => void | Promise<void>;
|
|
|
22
30
|
* - update: update an existing entry (e.g. mark as reviewed)
|
|
23
31
|
*/
|
|
24
32
|
export interface AuditLogProvider {
|
|
25
|
-
create: (params: {
|
|
33
|
+
create: (params: {
|
|
34
|
+
resource: string;
|
|
35
|
+
action: string;
|
|
36
|
+
timestamp?: string;
|
|
37
|
+
recordId?: string | number;
|
|
38
|
+
userId?: string;
|
|
39
|
+
outcome?: AuditEntry['outcome'];
|
|
40
|
+
data?: Record<string, unknown>;
|
|
41
|
+
previousData?: Record<string, unknown>;
|
|
42
|
+
meta?: Record<string, unknown>;
|
|
43
|
+
}) => Promise<AuditEntry>;
|
|
26
44
|
get: (params: { resource: string; action?: string; meta?: Record<string, unknown>; author?: Record<string, unknown> }) => Promise<AuditEntry[]>;
|
|
27
|
-
|
|
45
|
+
/** @deprecated 合规审计应保持追加写入;仅为旧实现兼容保留。 */
|
|
46
|
+
update?: (params: { id: string | number; name: string; meta?: Record<string, unknown> }) => Promise<AuditEntry>;
|
|
28
47
|
}
|
|
29
48
|
|
|
30
49
|
let handler: AuditHandler = (entry) => {
|
|
@@ -68,6 +87,10 @@ export function auditWithProvider(
|
|
|
68
87
|
provider.create({
|
|
69
88
|
resource: entry.resource ?? '',
|
|
70
89
|
action: entry.action,
|
|
90
|
+
timestamp: fullEntry.timestamp,
|
|
91
|
+
recordId: entry.recordId,
|
|
92
|
+
userId: entry.userId,
|
|
93
|
+
outcome: entry.outcome,
|
|
71
94
|
data: entry.data ?? entry.details,
|
|
72
95
|
previousData: entry.previousData,
|
|
73
96
|
meta: entry.meta,
|
|
@@ -78,3 +101,38 @@ export function auditWithProvider(
|
|
|
78
101
|
export function audit(entry: Omit<AuditEntry, 'timestamp'>): void {
|
|
79
102
|
auditWithProvider(entry, auditLogProvider);
|
|
80
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 严格写入审计记录。与兼容的 best-effort `audit` 不同,此函数会等待所有
|
|
107
|
+
* 写入并传播失败,适合权限、凭据、策略等必须和业务结果一起验证的流程。
|
|
108
|
+
*/
|
|
109
|
+
export async function writeAuditEntry(
|
|
110
|
+
entry: Omit<AuditEntry, 'timestamp'>,
|
|
111
|
+
provider: AuditLogProvider | null | undefined = auditLogProvider,
|
|
112
|
+
): Promise<AuditEntry> {
|
|
113
|
+
const fullEntry: AuditEntry = { ...entry, timestamp: new Date().toISOString() };
|
|
114
|
+
if (!provider) {
|
|
115
|
+
throw new Error('Strict audit logging requires an AuditLogProvider.');
|
|
116
|
+
}
|
|
117
|
+
await handler(fullEntry);
|
|
118
|
+
return provider.create({
|
|
119
|
+
resource: entry.resource ?? '',
|
|
120
|
+
action: entry.action,
|
|
121
|
+
timestamp: fullEntry.timestamp,
|
|
122
|
+
recordId: entry.recordId,
|
|
123
|
+
userId: entry.userId,
|
|
124
|
+
outcome: entry.outcome,
|
|
125
|
+
data: entry.data ?? entry.details,
|
|
126
|
+
previousData: entry.previousData,
|
|
127
|
+
meta: {
|
|
128
|
+
...(entry.meta ?? {}),
|
|
129
|
+
...(entry.outcome === undefined ? {} : { outcome: entry.outcome }),
|
|
130
|
+
...(entry.tenantId === undefined ? {} : { tenantId: entry.tenantId }),
|
|
131
|
+
...(entry.requestId === undefined ? {} : { requestId: entry.requestId }),
|
|
132
|
+
...(entry.traceId === undefined ? {} : { traceId: entry.traceId }),
|
|
133
|
+
...(entry.ipAddress === undefined ? {} : { ipAddress: entry.ipAddress }),
|
|
134
|
+
...(entry.userAgent === undefined ? {} : { userAgent: entry.userAgent }),
|
|
135
|
+
...(entry.error === undefined ? {} : { error: entry.error }),
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
package/src/auth-hooks.svelte.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { AuthActionResult, CheckResult, Identity, AuthProvider } from './ty
|
|
|
10
10
|
import { useQueryClient } from '@tanstack/svelte-query';
|
|
11
11
|
|
|
12
12
|
let _logoutVersion = $state(0);
|
|
13
|
+
let nextAuthMutationInstanceId = 0;
|
|
13
14
|
export function getLogoutVersion() { return _logoutVersion; }
|
|
14
15
|
export function resetLogoutVersion() { _logoutVersion = 0; }
|
|
15
16
|
|
|
@@ -22,7 +23,21 @@ interface CreateAuthMutationOptions {
|
|
|
22
23
|
onSuccess?: (result: AuthActionResult, adminContext: AdminContextAccessor) => void | Promise<void>;
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
export interface AuthNotificationOptions {
|
|
27
|
+
/** Set to false when the page owns the success state. */
|
|
28
|
+
successNotification?: string | false;
|
|
29
|
+
errorNotification?: string | false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveSuccessMessage(
|
|
33
|
+
configured: string | false | undefined,
|
|
34
|
+
defaultMessage: string,
|
|
35
|
+
): string | null {
|
|
36
|
+
return configured === false ? null : configured ?? defaultMessage;
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
function createAuthMutation(options: CreateAuthMutationOptions) {
|
|
40
|
+
const mutationInstanceId = nextAuthMutationInstanceId++;
|
|
26
41
|
const adminContext = captureAdminContext();
|
|
27
42
|
const i18n = useTranslation();
|
|
28
43
|
let isLoading = $state(false);
|
|
@@ -57,18 +72,31 @@ function createAuthMutation(options: CreateAuthMutationOptions) {
|
|
|
57
72
|
try {
|
|
58
73
|
const result = await fn.call(provider, params);
|
|
59
74
|
if (!isActiveMutation(epoch, provider, tenantIdentity)) return result;
|
|
75
|
+
const eventKey = `auth:${mutationInstanceId}:${String(options.method)}:${epoch}`;
|
|
60
76
|
if (result.success) {
|
|
61
|
-
if (options.successMessage)
|
|
77
|
+
if (options.successMessage) {
|
|
78
|
+
sendNotification({
|
|
79
|
+
type: 'success',
|
|
80
|
+
message: options.successMessage,
|
|
81
|
+
key: `${eventKey}:success`,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
62
84
|
if (options.onSuccess) await options.onSuccess(result, adminContext);
|
|
63
85
|
} else {
|
|
64
86
|
const msg = result.error?.message ?? (typeof options.errorMessage === 'string' ? options.errorMessage : i18n.t('common.operationFailed'));
|
|
65
|
-
if (options.errorMessage !== false)
|
|
87
|
+
if (options.errorMessage !== false) {
|
|
88
|
+
sendNotification({ type: 'error', message: msg, key: `${eventKey}:error` });
|
|
89
|
+
}
|
|
66
90
|
}
|
|
67
91
|
return result;
|
|
68
92
|
} catch (err) {
|
|
69
93
|
const msg = err instanceof Error ? err.message : (typeof options.errorMessage === 'string' ? options.errorMessage : i18n.t('common.operationFailed'));
|
|
70
94
|
if (isActiveMutation(epoch, provider, tenantIdentity) && options.errorMessage !== false) {
|
|
71
|
-
sendNotification({
|
|
95
|
+
sendNotification({
|
|
96
|
+
type: 'error',
|
|
97
|
+
message: msg,
|
|
98
|
+
key: `auth:${mutationInstanceId}:${String(options.method)}:${epoch}:error`,
|
|
99
|
+
});
|
|
72
100
|
}
|
|
73
101
|
return { success: false, error: { message: msg } };
|
|
74
102
|
} finally {
|
|
@@ -100,11 +128,11 @@ function createAuthMutation(options: CreateAuthMutationOptions) {
|
|
|
100
128
|
|
|
101
129
|
// ─── useLogin ─────────────────────────────────────────────────
|
|
102
130
|
|
|
103
|
-
export function useLogin(opts?: { errorMessage?: string | false }) {
|
|
131
|
+
export function useLogin(opts?: AuthNotificationOptions & { errorMessage?: string | false }) {
|
|
104
132
|
return createAuthMutation({
|
|
105
133
|
method: 'login',
|
|
106
|
-
successMessage: t('common.operationSuccess'),
|
|
107
|
-
errorMessage: opts?.errorMessage ?? t('common.loginFailed'),
|
|
134
|
+
successMessage: resolveSuccessMessage(opts?.successNotification, t('common.operationSuccess')),
|
|
135
|
+
errorMessage: opts?.errorNotification ?? opts?.errorMessage ?? t('common.loginFailed'),
|
|
108
136
|
onSuccess: async (result, adminContext) => { await adminContext.navigate(result.redirectTo ?? '/'); }
|
|
109
137
|
});
|
|
110
138
|
}
|
|
@@ -131,10 +159,11 @@ export function useLogout() {
|
|
|
131
159
|
|
|
132
160
|
// ─── useRegister ──────────────────────────────────────────────
|
|
133
161
|
|
|
134
|
-
export function useRegister() {
|
|
162
|
+
export function useRegister(opts?: AuthNotificationOptions) {
|
|
135
163
|
return createAuthMutation({
|
|
136
164
|
method: 'register',
|
|
137
|
-
successMessage: t('auth.registerSuccess'),
|
|
165
|
+
successMessage: resolveSuccessMessage(opts?.successNotification, t('auth.registerSuccess')),
|
|
166
|
+
errorMessage: opts?.errorNotification,
|
|
138
167
|
onSuccess: async (result, adminContext) => {
|
|
139
168
|
if (result.redirectTo) await adminContext.navigate(result.redirectTo);
|
|
140
169
|
}
|
|
@@ -143,19 +172,21 @@ export function useRegister() {
|
|
|
143
172
|
|
|
144
173
|
// ─── useForgotPassword ───────────────────────────────────────
|
|
145
174
|
|
|
146
|
-
export function useForgotPassword() {
|
|
175
|
+
export function useForgotPassword(opts?: AuthNotificationOptions) {
|
|
147
176
|
return createAuthMutation({
|
|
148
177
|
method: 'forgotPassword',
|
|
149
|
-
successMessage: t('auth.resetLinkSent')
|
|
178
|
+
successMessage: resolveSuccessMessage(opts?.successNotification, t('auth.resetLinkSent')),
|
|
179
|
+
errorMessage: opts?.errorNotification,
|
|
150
180
|
});
|
|
151
181
|
}
|
|
152
182
|
|
|
153
183
|
// ─── useUpdatePassword ───────────────────────────────────────
|
|
154
184
|
|
|
155
|
-
export function useUpdatePassword() {
|
|
185
|
+
export function useUpdatePassword(opts?: AuthNotificationOptions) {
|
|
156
186
|
return createAuthMutation({
|
|
157
187
|
method: 'updatePassword',
|
|
158
|
-
successMessage: t('common.operationSuccess'),
|
|
188
|
+
successMessage: resolveSuccessMessage(opts?.successNotification, t('common.operationSuccess')),
|
|
189
|
+
errorMessage: opts?.errorNotification,
|
|
159
190
|
onSuccess: async (result, adminContext) => {
|
|
160
191
|
if (result.redirectTo) await adminContext.navigate(result.redirectTo);
|
|
161
192
|
}
|
|
@@ -164,20 +195,22 @@ export function useUpdatePassword() {
|
|
|
164
195
|
|
|
165
196
|
// ─── useUpdateIdentity / useUpdateProfile ────────────────────
|
|
166
197
|
|
|
167
|
-
export function useUpdateIdentity() {
|
|
198
|
+
export function useUpdateIdentity(opts?: AuthNotificationOptions) {
|
|
168
199
|
return createAuthMutation({
|
|
169
200
|
method: 'updateIdentity',
|
|
170
|
-
successMessage: t('common.operationSuccess'),
|
|
201
|
+
successMessage: resolveSuccessMessage(opts?.successNotification, t('common.operationSuccess')),
|
|
202
|
+
errorMessage: opts?.errorNotification,
|
|
171
203
|
onSuccess: async (result, adminContext) => {
|
|
172
204
|
if (result.redirectTo) await adminContext.navigate(result.redirectTo);
|
|
173
205
|
}
|
|
174
206
|
});
|
|
175
207
|
}
|
|
176
208
|
|
|
177
|
-
export function useUpdateProfile() {
|
|
209
|
+
export function useUpdateProfile(opts?: AuthNotificationOptions) {
|
|
178
210
|
return createAuthMutation({
|
|
179
211
|
method: 'updateProfile',
|
|
180
|
-
successMessage: t('common.operationSuccess'),
|
|
212
|
+
successMessage: resolveSuccessMessage(opts?.successNotification, t('common.operationSuccess')),
|
|
213
|
+
errorMessage: opts?.errorNotification,
|
|
181
214
|
onSuccess: async (result, adminContext) => {
|
|
182
215
|
if (result.redirectTo) await adminContext.navigate(result.redirectTo);
|
|
183
216
|
}
|
package/src/context.svelte.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { LiveProvider } from './live.svelte';
|
|
|
8
8
|
import type { AccessControlProvider } from './permissions.svelte';
|
|
9
9
|
import type { AuditLogProvider } from './audit';
|
|
10
10
|
import type { AgentProvider, ChatProvider } from './chatProvider.svelte';
|
|
11
|
+
import type { EnterpriseRequestContext } from './enterprise';
|
|
11
12
|
import {
|
|
12
13
|
createTenantCacheKey,
|
|
13
14
|
resolveTenantProviderMeta,
|
|
@@ -77,6 +78,10 @@ export interface AdminContextValue {
|
|
|
77
78
|
readonly notificationProvider: ProviderBundle['notificationProvider'];
|
|
78
79
|
readonly chatProvider: ChatProvider | null;
|
|
79
80
|
readonly agentProvider: AgentProvider | null;
|
|
81
|
+
readonly organizationProvider: ProviderBundle['organizationProvider'];
|
|
82
|
+
readonly identityGovernanceProvider: ProviderBundle['identityGovernanceProvider'];
|
|
83
|
+
readonly sessionProvider: ProviderBundle['sessionProvider'];
|
|
84
|
+
readonly credentialProvider: ProviderBundle['credentialProvider'];
|
|
80
85
|
readonly tenant: TenantContext | undefined;
|
|
81
86
|
readonly tenantAdapter: TenantAdapter | undefined;
|
|
82
87
|
}
|
|
@@ -102,9 +107,14 @@ export interface AdminContextAccessor {
|
|
|
102
107
|
readonly notificationProvider: ProviderBundle['notificationProvider'];
|
|
103
108
|
readonly chatProvider: ChatProvider | null;
|
|
104
109
|
readonly agentProvider: AgentProvider | null;
|
|
110
|
+
readonly organizationProvider: ProviderBundle['organizationProvider'];
|
|
111
|
+
readonly identityGovernanceProvider: ProviderBundle['identityGovernanceProvider'];
|
|
112
|
+
readonly sessionProvider: ProviderBundle['sessionProvider'];
|
|
113
|
+
readonly credentialProvider: ProviderBundle['credentialProvider'];
|
|
105
114
|
readonly tenant: TenantContext | undefined;
|
|
106
115
|
readonly tenantAdapter: TenantAdapter | undefined;
|
|
107
116
|
readonly tenantCacheKey: TenantCacheKey | undefined;
|
|
117
|
+
readonly enterpriseRequestContext: EnterpriseRequestContext;
|
|
108
118
|
getDataProvider(name?: string): DataProvider;
|
|
109
119
|
getDataProviderNames(): string[];
|
|
110
120
|
resolveDataProviderName(resourceName: string, overrideName?: string): string;
|
|
@@ -166,6 +176,18 @@ function resolveProviderBundle(source: AdminContextSource): ProviderBundle {
|
|
|
166
176
|
taskProvider: source.taskProvider ?? bundled?.taskProvider,
|
|
167
177
|
routerProvider: source.routerProvider ?? bundled?.routerProvider,
|
|
168
178
|
tenantAdapter: source.tenantAdapter ?? bundled?.tenantAdapter,
|
|
179
|
+
organizationProvider: source.organizationProvider !== undefined
|
|
180
|
+
? source.organizationProvider
|
|
181
|
+
: bundled?.organizationProvider,
|
|
182
|
+
identityGovernanceProvider: source.identityGovernanceProvider !== undefined
|
|
183
|
+
? source.identityGovernanceProvider
|
|
184
|
+
: bundled?.identityGovernanceProvider,
|
|
185
|
+
sessionProvider: source.sessionProvider !== undefined
|
|
186
|
+
? source.sessionProvider
|
|
187
|
+
: bundled?.sessionProvider,
|
|
188
|
+
credentialProvider: source.credentialProvider !== undefined
|
|
189
|
+
? source.credentialProvider
|
|
190
|
+
: bundled?.credentialProvider,
|
|
169
191
|
};
|
|
170
192
|
}
|
|
171
193
|
|
|
@@ -184,6 +206,10 @@ export function createAdminContext(source: AdminContextSource): AdminContextValu
|
|
|
184
206
|
get notificationProvider() { return resolveProviderBundle(source).notificationProvider ?? null; },
|
|
185
207
|
get chatProvider() { return resolveProviderBundle(source).chatProvider ?? null; },
|
|
186
208
|
get agentProvider() { return resolveProviderBundle(source).agentProvider ?? null; },
|
|
209
|
+
get organizationProvider() { return resolveProviderBundle(source).organizationProvider ?? null; },
|
|
210
|
+
get identityGovernanceProvider() { return resolveProviderBundle(source).identityGovernanceProvider ?? null; },
|
|
211
|
+
get sessionProvider() { return resolveProviderBundle(source).sessionProvider ?? null; },
|
|
212
|
+
get credentialProvider() { return resolveProviderBundle(source).credentialProvider ?? null; },
|
|
187
213
|
get tenant() { return source.tenant ?? inheritedTenant; },
|
|
188
214
|
get tenantAdapter() { return resolveProviderBundle(source).tenantAdapter; },
|
|
189
215
|
};
|
|
@@ -254,11 +280,24 @@ export function captureAdminContext(): AdminContextAccessor {
|
|
|
254
280
|
},
|
|
255
281
|
get chatProvider() { return scopedContext ? scopedContext.chatProvider : getLegacyChatProvider(); },
|
|
256
282
|
get agentProvider() { return scopedContext ? scopedContext.agentProvider : getLegacyAgentProvider(); },
|
|
283
|
+
get organizationProvider() { return scopedContext?.organizationProvider ?? null; },
|
|
284
|
+
get identityGovernanceProvider() { return scopedContext?.identityGovernanceProvider ?? null; },
|
|
285
|
+
get sessionProvider() { return scopedContext?.sessionProvider ?? null; },
|
|
286
|
+
get credentialProvider() { return scopedContext?.credentialProvider ?? null; },
|
|
257
287
|
get tenant() { return scopedContext?.tenant; },
|
|
258
288
|
get tenantAdapter() { return scopedContext?.tenantAdapter; },
|
|
259
289
|
get tenantCacheKey() {
|
|
260
290
|
return accessor.tenant ? createTenantCacheKey(accessor.tenant, accessor.tenantAdapter) : undefined;
|
|
261
291
|
},
|
|
292
|
+
get enterpriseRequestContext() {
|
|
293
|
+
const tenant = accessor.tenant;
|
|
294
|
+
return {
|
|
295
|
+
tenantId: tenant?.tenantId,
|
|
296
|
+
requestId: typeof tenant?.meta?.requestId === 'string' ? tenant.meta.requestId : undefined,
|
|
297
|
+
traceId: typeof tenant?.meta?.traceId === 'string' ? tenant.meta.traceId : undefined,
|
|
298
|
+
meta: tenant?.meta,
|
|
299
|
+
};
|
|
300
|
+
},
|
|
262
301
|
getDataProvider(name) {
|
|
263
302
|
const activeProviders = accessor.providers;
|
|
264
303
|
if (!activeProviders) throw new Error('DataProvider not found. Did you call setDataProvider in App.svelte?');
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/** 企业级管理能力的正式 Provider 契约。 */
|
|
2
|
+
|
|
3
|
+
export interface EnterpriseActionError {
|
|
4
|
+
message: string;
|
|
5
|
+
code?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface EnterpriseActionResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
error?: EnterpriseActionError;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 每次企业能力调用都必须显式携带当前请求树的租户与追踪上下文。 */
|
|
14
|
+
export interface EnterpriseRequestContext {
|
|
15
|
+
tenantId?: string | number;
|
|
16
|
+
requestId?: string;
|
|
17
|
+
traceId?: string;
|
|
18
|
+
meta?: Readonly<Record<string, unknown>>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Organization {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
slug?: string;
|
|
25
|
+
domain?: string;
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface OrganizationProvider {
|
|
30
|
+
getCurrentOrganization: (context: EnterpriseRequestContext) => Promise<Organization | null>;
|
|
31
|
+
updateCurrentOrganization?: (
|
|
32
|
+
input: Partial<Omit<Organization, 'id'>>,
|
|
33
|
+
context: EnterpriseRequestContext,
|
|
34
|
+
) => Promise<Organization>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface EnterpriseSecurityPolicy {
|
|
38
|
+
sessionTimeoutMinutes: number;
|
|
39
|
+
auditRetentionDays: number;
|
|
40
|
+
auditLoggingEnabled: boolean;
|
|
41
|
+
requireSso: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type IdentityProviderProtocol = 'oidc' | 'saml' | (string & {});
|
|
45
|
+
export type IdentityProviderStatus = 'connected' | 'disabled' | 'error' | (string & {});
|
|
46
|
+
|
|
47
|
+
export interface IdentityProviderSummary {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
protocol: IdentityProviderProtocol;
|
|
51
|
+
status: IdentityProviderStatus;
|
|
52
|
+
metadataUrl?: string;
|
|
53
|
+
domain?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface EnterpriseSecurityEvent {
|
|
57
|
+
id: string;
|
|
58
|
+
event: string;
|
|
59
|
+
actor?: string;
|
|
60
|
+
location?: string;
|
|
61
|
+
createdAt: string | Date;
|
|
62
|
+
severity?: 'info' | 'warning' | 'critical';
|
|
63
|
+
tenantId?: string | number;
|
|
64
|
+
requestId?: string;
|
|
65
|
+
traceId?: string;
|
|
66
|
+
metadata?: Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface IdentityGovernanceProvider {
|
|
70
|
+
getSecurityPolicy: (context: EnterpriseRequestContext) => Promise<EnterpriseSecurityPolicy>;
|
|
71
|
+
updateSecurityPolicy: (
|
|
72
|
+
policy: EnterpriseSecurityPolicy,
|
|
73
|
+
context: EnterpriseRequestContext,
|
|
74
|
+
) => Promise<EnterpriseSecurityPolicy>;
|
|
75
|
+
listIdentityProviders: (context: EnterpriseRequestContext) => Promise<IdentityProviderSummary[]>;
|
|
76
|
+
testIdentityProvider: (params: {
|
|
77
|
+
id?: string;
|
|
78
|
+
protocol?: IdentityProviderProtocol;
|
|
79
|
+
metadataUrl?: string;
|
|
80
|
+
}, context: EnterpriseRequestContext) => Promise<EnterpriseActionResult>;
|
|
81
|
+
listSecurityEvents?: (params: {
|
|
82
|
+
page?: number;
|
|
83
|
+
pageSize?: number;
|
|
84
|
+
} | undefined, context: EnterpriseRequestContext) => Promise<{ data: EnterpriseSecurityEvent[]; total: number }>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface SessionInfo {
|
|
88
|
+
id: string;
|
|
89
|
+
os?: string;
|
|
90
|
+
browser?: string;
|
|
91
|
+
ipAddress?: string;
|
|
92
|
+
lastActiveAt?: string | Date;
|
|
93
|
+
current: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface MfaState {
|
|
97
|
+
enabled: boolean;
|
|
98
|
+
methods?: string[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface SessionProvider {
|
|
102
|
+
listSessions: (context: EnterpriseRequestContext) => Promise<SessionInfo[]>;
|
|
103
|
+
revokeSession: (sessionId: string, context: EnterpriseRequestContext) => Promise<EnterpriseActionResult>;
|
|
104
|
+
revokeOtherSessions: (context: EnterpriseRequestContext) => Promise<EnterpriseActionResult>;
|
|
105
|
+
getMfaState?: (context: EnterpriseRequestContext) => Promise<MfaState>;
|
|
106
|
+
setMfaEnabled?: (enabled: boolean, context: EnterpriseRequestContext) => Promise<MfaState>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ApiCredentialSummary {
|
|
110
|
+
id: string;
|
|
111
|
+
name: string;
|
|
112
|
+
prefix: string;
|
|
113
|
+
createdAt: string | Date;
|
|
114
|
+
lastUsedAt?: string | Date;
|
|
115
|
+
permissions: string[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface CreatedApiCredential {
|
|
119
|
+
credential: ApiCredentialSummary;
|
|
120
|
+
/** 只在创建时返回一次;列表接口不得返回此字段。 */
|
|
121
|
+
secret: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface WebhookSummary {
|
|
125
|
+
id: string;
|
|
126
|
+
name: string;
|
|
127
|
+
url: string;
|
|
128
|
+
eventType: string;
|
|
129
|
+
enabled?: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface CredentialProvider {
|
|
133
|
+
listApiCredentials: (context: EnterpriseRequestContext) => Promise<ApiCredentialSummary[]>;
|
|
134
|
+
createApiCredential: (params: {
|
|
135
|
+
name: string;
|
|
136
|
+
permissions: string[];
|
|
137
|
+
}, context: EnterpriseRequestContext) => Promise<CreatedApiCredential>;
|
|
138
|
+
revokeApiCredential: (credentialId: string, context: EnterpriseRequestContext) => Promise<EnterpriseActionResult>;
|
|
139
|
+
listWebhooks: (context: EnterpriseRequestContext) => Promise<WebhookSummary[]>;
|
|
140
|
+
createWebhook: (params: {
|
|
141
|
+
name: string;
|
|
142
|
+
url: string;
|
|
143
|
+
eventType: string;
|
|
144
|
+
}, context: EnterpriseRequestContext) => Promise<WebhookSummary>;
|
|
145
|
+
deleteWebhook: (webhookId: string, context: EnterpriseRequestContext) => Promise<EnterpriseActionResult>;
|
|
146
|
+
}
|
package/src/hook-utils.svelte.ts
CHANGED
|
@@ -128,7 +128,12 @@ export function createLiveSubscription(paramsFn: () => LiveSubscriptionParams):
|
|
|
128
128
|
export type NotificationConfig =
|
|
129
129
|
| string
|
|
130
130
|
| false
|
|
131
|
-
| ((data?: unknown, values?: unknown, resource?: string) => {
|
|
131
|
+
| ((data?: unknown, values?: unknown, resource?: string) => {
|
|
132
|
+
message: string;
|
|
133
|
+
description?: string;
|
|
134
|
+
type?: 'success' | 'error';
|
|
135
|
+
key?: string;
|
|
136
|
+
})
|
|
132
137
|
| undefined;
|
|
133
138
|
|
|
134
139
|
export interface SuccessNotificationRequest {
|
|
@@ -154,7 +159,12 @@ export function fireSuccessNotification(request: SuccessNotificationRequest): vo
|
|
|
154
159
|
if (!config && !defaultMessage) return;
|
|
155
160
|
if (typeof config === 'function') {
|
|
156
161
|
const result = config(data, values, resource);
|
|
157
|
-
notifyWithProvider({
|
|
162
|
+
notifyWithProvider({
|
|
163
|
+
type: 'success',
|
|
164
|
+
message: result.message,
|
|
165
|
+
description: result.description,
|
|
166
|
+
key: result.key,
|
|
167
|
+
}, provider);
|
|
158
168
|
return;
|
|
159
169
|
}
|
|
160
170
|
notifyWithProvider({ type: 'success', message: config || defaultMessage }, provider);
|
|
@@ -166,7 +176,12 @@ export function fireErrorNotification(request: ErrorNotificationRequest): void {
|
|
|
166
176
|
if (!config && !defaultMessage) return;
|
|
167
177
|
if (typeof config === 'function') {
|
|
168
178
|
const result = config(error, undefined, resource);
|
|
169
|
-
notifyWithProvider({
|
|
179
|
+
notifyWithProvider({
|
|
180
|
+
type: 'error',
|
|
181
|
+
message: result.message,
|
|
182
|
+
description: result.description,
|
|
183
|
+
key: result.key,
|
|
184
|
+
}, provider);
|
|
170
185
|
return;
|
|
171
186
|
}
|
|
172
187
|
const errMsg = error instanceof Error ? error.message : String(error ?? '');
|
package/src/i18n.svelte.ts
CHANGED
|
@@ -90,6 +90,7 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
90
90
|
'common.toggleSidebar': '切换侧边栏',
|
|
91
91
|
'common.configRequired': '需要配置',
|
|
92
92
|
'common.removeImage': '移除图片',
|
|
93
|
+
'common.skipToMainContent': '跳到主要内容',
|
|
93
94
|
// Field
|
|
94
95
|
'field.enterValue': '输入{label}',
|
|
95
96
|
'field.selectPlaceholder': '请选择',
|
|
@@ -125,6 +126,8 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
125
126
|
'auth.forgotPasswordDescription': '输入您的邮箱地址,我们将发送重置密码的链接。',
|
|
126
127
|
'auth.resetPasswordDescription': '设置一个新的登录密码。',
|
|
127
128
|
'auth.resetLinkSent': '重置链接已发送,请查看您的邮箱。',
|
|
129
|
+
'auth.resetLinkSentTitle': '重置链接已发送',
|
|
130
|
+
'auth.resetLinkSentDescription': '如果该账号存在,请按照邮件中的步骤重置密码。',
|
|
128
131
|
'auth.passwordMismatch': '两次输入的密码不一致',
|
|
129
132
|
'auth.emailRequired': '请输入邮箱',
|
|
130
133
|
'auth.passwordRequired': '请输入密码',
|
|
@@ -719,6 +722,7 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
719
722
|
'common.toggleSidebar': 'Toggle sidebar',
|
|
720
723
|
'common.configRequired': 'Configuration Required',
|
|
721
724
|
'common.removeImage': 'Remove image',
|
|
725
|
+
'common.skipToMainContent': 'Skip to main content',
|
|
722
726
|
// Field
|
|
723
727
|
'field.enterValue': 'Enter {label}',
|
|
724
728
|
'field.selectPlaceholder': 'Select...',
|
|
@@ -754,6 +758,8 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
754
758
|
'auth.forgotPasswordDescription': 'Enter your email address and we will send you a reset link.',
|
|
755
759
|
'auth.resetPasswordDescription': 'Set a new sign-in password.',
|
|
756
760
|
'auth.resetLinkSent': 'Reset link sent! Check your email.',
|
|
761
|
+
'auth.resetLinkSentTitle': 'Reset link sent',
|
|
762
|
+
'auth.resetLinkSentDescription': 'If the account exists, follow the instructions in the email to reset the password.',
|
|
757
763
|
'auth.passwordMismatch': 'Passwords do not match',
|
|
758
764
|
'auth.emailRequired': 'Email is required',
|
|
759
765
|
'auth.passwordRequired': 'Password is required',
|
package/src/index.ts
CHANGED
|
@@ -84,8 +84,28 @@ export {
|
|
|
84
84
|
export type { NotificationParams } from './notification.svelte';
|
|
85
85
|
export { t, setLocale, getLocale, getAvailableLocales, addTranslations, useTranslation, setI18nProvider, getI18nProvider, createI18nScope, provideI18nScope, getI18nScope, resetI18n } from './i18n.svelte';
|
|
86
86
|
export type { I18nProvider, I18nScope, I18nScopeOptions } from './i18n.svelte';
|
|
87
|
-
export { audit, auditWithProvider, setAuditHandler, setAuditLogProvider, getAuditLogProvider } from './audit';
|
|
87
|
+
export { audit, auditWithProvider, writeAuditEntry, setAuditHandler, setAuditLogProvider, getAuditLogProvider } from './audit';
|
|
88
88
|
export type { AuditLogProvider } from './audit';
|
|
89
|
+
export type {
|
|
90
|
+
ApiCredentialSummary,
|
|
91
|
+
CreatedApiCredential,
|
|
92
|
+
CredentialProvider,
|
|
93
|
+
EnterpriseActionError,
|
|
94
|
+
EnterpriseActionResult,
|
|
95
|
+
EnterpriseRequestContext,
|
|
96
|
+
EnterpriseSecurityEvent,
|
|
97
|
+
EnterpriseSecurityPolicy,
|
|
98
|
+
IdentityGovernanceProvider,
|
|
99
|
+
IdentityProviderProtocol,
|
|
100
|
+
IdentityProviderStatus,
|
|
101
|
+
IdentityProviderSummary,
|
|
102
|
+
MfaState,
|
|
103
|
+
Organization,
|
|
104
|
+
OrganizationProvider,
|
|
105
|
+
SessionInfo,
|
|
106
|
+
SessionProvider,
|
|
107
|
+
WebhookSummary,
|
|
108
|
+
} from './enterprise';
|
|
89
109
|
export { setChatProvider, getChatProvider, setChatContext, getChatContext, setAgentProvider, getAgentProvider, registerApproval, resolveApproval, hasPendingApprovals, resetChatProvider } from './chatProvider.svelte';
|
|
90
110
|
export type { ChatProvider, ChatMessage, ChatContext, ChatAction, AgentProvider, AgentEvent, AgentOptions, AdminTool, AdminToolParameter, ToolResult } from './chatProvider.svelte';
|
|
91
111
|
export { getTheme, setTheme, toggleTheme, getResolvedTheme, getColorTheme, setColorTheme, getColorThemes, configureTheme, getThemeConfig, clearCssOverrides, builtinPresets, registerColorPreset, getColorPresets, registerThemeOwner, updateThemeOwner, unregisterThemeOwner, resetTheme } from './theme.svelte';
|
|
@@ -136,6 +156,7 @@ export {
|
|
|
136
156
|
useOnError, usePermissions,
|
|
137
157
|
getLogoutVersion, resetLogoutVersion,
|
|
138
158
|
} from './auth-hooks.svelte';
|
|
159
|
+
export type { AuthNotificationOptions } from './auth-hooks.svelte';
|
|
139
160
|
export {
|
|
140
161
|
useSubmitTask,
|
|
141
162
|
useTask,
|
package/src/provider-bundle.ts
CHANGED
|
@@ -6,6 +6,12 @@ import type { AuditLogProvider } from './audit';
|
|
|
6
6
|
import type { AccessControlProvider } from './permissions.svelte';
|
|
7
7
|
import type { LiveProvider } from './live.svelte';
|
|
8
8
|
import type { RouterProvider } from './router-provider';
|
|
9
|
+
import type {
|
|
10
|
+
CredentialProvider,
|
|
11
|
+
IdentityGovernanceProvider,
|
|
12
|
+
OrganizationProvider,
|
|
13
|
+
SessionProvider,
|
|
14
|
+
} from './enterprise';
|
|
9
15
|
import type {
|
|
10
16
|
AuthProvider,
|
|
11
17
|
BaseRecord,
|
|
@@ -52,6 +58,10 @@ export interface ProviderBundle {
|
|
|
52
58
|
readonly taskProvider?: TaskProvider;
|
|
53
59
|
readonly routerProvider?: RouterProvider;
|
|
54
60
|
readonly tenantAdapter?: TenantAdapter;
|
|
61
|
+
readonly organizationProvider?: OrganizationProvider | null;
|
|
62
|
+
readonly identityGovernanceProvider?: IdentityGovernanceProvider | null;
|
|
63
|
+
readonly sessionProvider?: SessionProvider | null;
|
|
64
|
+
readonly credentialProvider?: CredentialProvider | null;
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
/** 保留传入对象的精确类型,同时执行 ProviderBundle 的编译期校验。 */
|