@svadmin/core 0.38.0 → 0.39.1
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 +1 -1
- package/src/audit.ts +60 -2
- package/src/context.svelte.ts +39 -0
- package/src/enterprise.ts +206 -0
- package/src/i18n.svelte.ts +2 -0
- package/src/index.ts +25 -1
- package/src/provider-bundle.ts +10 -0
- package/src/route-parsing.ts +14 -0
- package/src/useParsed.svelte.ts +3 -36
package/package.json
CHANGED
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/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,206 @@
|
|
|
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
|
+
/** Provider 调用使用的严格请求上下文;三项边界标识不可省略。 */
|
|
22
|
+
export interface StrictEnterpriseRequestContext {
|
|
23
|
+
tenantId: string | number;
|
|
24
|
+
requestId: string;
|
|
25
|
+
traceId: string;
|
|
26
|
+
meta?: Readonly<Record<string, unknown>>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface EnterpriseRequestContextInput {
|
|
30
|
+
tenantId: string | number;
|
|
31
|
+
requestId?: string;
|
|
32
|
+
traceId?: string;
|
|
33
|
+
meta?: Readonly<Record<string, unknown>>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function nonEmpty(value: string, field: string): string {
|
|
37
|
+
const normalized = value.trim();
|
|
38
|
+
if (!normalized) throw new Error(`${field} must not be empty.`);
|
|
39
|
+
return normalized;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function validatedTenantId(tenantId: string | number): string | number {
|
|
43
|
+
if (typeof tenantId === 'string') return nonEmpty(tenantId, 'tenantId');
|
|
44
|
+
if (!Number.isFinite(tenantId)) throw new Error('tenantId must be a finite number.');
|
|
45
|
+
return tenantId;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createRequestId(): string {
|
|
49
|
+
const randomUUID = globalThis.crypto?.randomUUID;
|
|
50
|
+
if (!randomUUID) throw new Error('crypto.randomUUID is required to create an enterprise request context.');
|
|
51
|
+
return randomUUID.call(globalThis.crypto);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Create a validated provider context while preserving one trace across the request tree. */
|
|
55
|
+
export function createEnterpriseRequestContext(
|
|
56
|
+
input: EnterpriseRequestContextInput,
|
|
57
|
+
): StrictEnterpriseRequestContext {
|
|
58
|
+
const requestId = nonEmpty(input.requestId || createRequestId(), 'requestId');
|
|
59
|
+
return {
|
|
60
|
+
tenantId: validatedTenantId(input.tenantId),
|
|
61
|
+
requestId,
|
|
62
|
+
traceId: nonEmpty(input.traceId || requestId, 'traceId'),
|
|
63
|
+
...(input.meta ? { meta: input.meta } : {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Narrow a legacy optional context at the provider boundary and fail closed on missing identity. */
|
|
68
|
+
export function assertEnterpriseRequestContext(
|
|
69
|
+
context: EnterpriseRequestContext,
|
|
70
|
+
): asserts context is StrictEnterpriseRequestContext {
|
|
71
|
+
if (context.tenantId === undefined || context.tenantId === null) {
|
|
72
|
+
throw new Error('tenantId must not be empty.');
|
|
73
|
+
}
|
|
74
|
+
validatedTenantId(context.tenantId);
|
|
75
|
+
nonEmpty(context.requestId ?? '', 'requestId');
|
|
76
|
+
nonEmpty(context.traceId ?? '', 'traceId');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type EnterpriseProviderRequestContext = StrictEnterpriseRequestContext;
|
|
80
|
+
|
|
81
|
+
export interface Organization {
|
|
82
|
+
id: string;
|
|
83
|
+
name: string;
|
|
84
|
+
slug?: string;
|
|
85
|
+
domain?: string;
|
|
86
|
+
metadata?: Record<string, unknown>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface OrganizationProvider {
|
|
90
|
+
getCurrentOrganization: (context: EnterpriseRequestContext) => Promise<Organization | null>;
|
|
91
|
+
updateCurrentOrganization?: (
|
|
92
|
+
input: Partial<Omit<Organization, 'id'>>,
|
|
93
|
+
context: EnterpriseRequestContext,
|
|
94
|
+
) => Promise<Organization>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface EnterpriseSecurityPolicy {
|
|
98
|
+
sessionTimeoutMinutes: number;
|
|
99
|
+
auditRetentionDays: number;
|
|
100
|
+
auditLoggingEnabled: boolean;
|
|
101
|
+
requireSso: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type IdentityProviderProtocol = 'oidc' | 'saml' | (string & {});
|
|
105
|
+
export type IdentityProviderStatus = 'connected' | 'disabled' | 'error' | (string & {});
|
|
106
|
+
|
|
107
|
+
export interface IdentityProviderSummary {
|
|
108
|
+
id: string;
|
|
109
|
+
name: string;
|
|
110
|
+
protocol: IdentityProviderProtocol;
|
|
111
|
+
status: IdentityProviderStatus;
|
|
112
|
+
metadataUrl?: string;
|
|
113
|
+
domain?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface EnterpriseSecurityEvent {
|
|
117
|
+
id: string;
|
|
118
|
+
event: string;
|
|
119
|
+
actor?: string;
|
|
120
|
+
location?: string;
|
|
121
|
+
createdAt: string | Date;
|
|
122
|
+
severity?: 'info' | 'warning' | 'critical';
|
|
123
|
+
tenantId?: string | number;
|
|
124
|
+
requestId?: string;
|
|
125
|
+
traceId?: string;
|
|
126
|
+
metadata?: Record<string, unknown>;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface IdentityGovernanceProvider {
|
|
130
|
+
getSecurityPolicy: (context: EnterpriseRequestContext) => Promise<EnterpriseSecurityPolicy>;
|
|
131
|
+
updateSecurityPolicy: (
|
|
132
|
+
policy: EnterpriseSecurityPolicy,
|
|
133
|
+
context: EnterpriseRequestContext,
|
|
134
|
+
) => Promise<EnterpriseSecurityPolicy>;
|
|
135
|
+
listIdentityProviders: (context: EnterpriseRequestContext) => Promise<IdentityProviderSummary[]>;
|
|
136
|
+
testIdentityProvider: (params: {
|
|
137
|
+
id?: string;
|
|
138
|
+
protocol?: IdentityProviderProtocol;
|
|
139
|
+
metadataUrl?: string;
|
|
140
|
+
}, context: EnterpriseRequestContext) => Promise<EnterpriseActionResult>;
|
|
141
|
+
listSecurityEvents?: (params: {
|
|
142
|
+
page?: number;
|
|
143
|
+
pageSize?: number;
|
|
144
|
+
} | undefined, context: EnterpriseRequestContext) => Promise<{ data: EnterpriseSecurityEvent[]; total: number }>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface SessionInfo {
|
|
148
|
+
id: string;
|
|
149
|
+
os?: string;
|
|
150
|
+
browser?: string;
|
|
151
|
+
ipAddress?: string;
|
|
152
|
+
lastActiveAt?: string | Date;
|
|
153
|
+
current: boolean;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface MfaState {
|
|
157
|
+
enabled: boolean;
|
|
158
|
+
methods?: string[];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface SessionProvider {
|
|
162
|
+
listSessions: (context: EnterpriseRequestContext) => Promise<SessionInfo[]>;
|
|
163
|
+
revokeSession: (sessionId: string, context: EnterpriseRequestContext) => Promise<EnterpriseActionResult>;
|
|
164
|
+
revokeOtherSessions: (context: EnterpriseRequestContext) => Promise<EnterpriseActionResult>;
|
|
165
|
+
getMfaState?: (context: EnterpriseRequestContext) => Promise<MfaState>;
|
|
166
|
+
setMfaEnabled?: (enabled: boolean, context: EnterpriseRequestContext) => Promise<MfaState>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface ApiCredentialSummary {
|
|
170
|
+
id: string;
|
|
171
|
+
name: string;
|
|
172
|
+
prefix: string;
|
|
173
|
+
createdAt: string | Date;
|
|
174
|
+
lastUsedAt?: string | Date;
|
|
175
|
+
permissions: string[];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface CreatedApiCredential {
|
|
179
|
+
credential: ApiCredentialSummary;
|
|
180
|
+
/** 只在创建时返回一次;列表接口不得返回此字段。 */
|
|
181
|
+
secret: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface WebhookSummary {
|
|
185
|
+
id: string;
|
|
186
|
+
name: string;
|
|
187
|
+
url: string;
|
|
188
|
+
eventType: string;
|
|
189
|
+
enabled?: boolean;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface CredentialProvider {
|
|
193
|
+
listApiCredentials: (context: EnterpriseRequestContext) => Promise<ApiCredentialSummary[]>;
|
|
194
|
+
createApiCredential: (params: {
|
|
195
|
+
name: string;
|
|
196
|
+
permissions: string[];
|
|
197
|
+
}, context: EnterpriseRequestContext) => Promise<CreatedApiCredential>;
|
|
198
|
+
revokeApiCredential: (credentialId: string, context: EnterpriseRequestContext) => Promise<EnterpriseActionResult>;
|
|
199
|
+
listWebhooks: (context: EnterpriseRequestContext) => Promise<WebhookSummary[]>;
|
|
200
|
+
createWebhook: (params: {
|
|
201
|
+
name: string;
|
|
202
|
+
url: string;
|
|
203
|
+
eventType: string;
|
|
204
|
+
}, context: EnterpriseRequestContext) => Promise<WebhookSummary>;
|
|
205
|
+
deleteWebhook: (webhookId: string, context: EnterpriseRequestContext) => Promise<EnterpriseActionResult>;
|
|
206
|
+
}
|
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': '请选择',
|
|
@@ -721,6 +722,7 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
721
722
|
'common.toggleSidebar': 'Toggle sidebar',
|
|
722
723
|
'common.configRequired': 'Configuration Required',
|
|
723
724
|
'common.removeImage': 'Remove image',
|
|
725
|
+
'common.skipToMainContent': 'Skip to main content',
|
|
724
726
|
// Field
|
|
725
727
|
'field.enterValue': 'Enter {label}',
|
|
726
728
|
'field.selectPlaceholder': 'Select...',
|
package/src/index.ts
CHANGED
|
@@ -84,8 +84,32 @@ 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
|
+
EnterpriseRequestContextInput,
|
|
97
|
+
EnterpriseProviderRequestContext,
|
|
98
|
+
StrictEnterpriseRequestContext,
|
|
99
|
+
EnterpriseSecurityEvent,
|
|
100
|
+
EnterpriseSecurityPolicy,
|
|
101
|
+
IdentityGovernanceProvider,
|
|
102
|
+
IdentityProviderProtocol,
|
|
103
|
+
IdentityProviderStatus,
|
|
104
|
+
IdentityProviderSummary,
|
|
105
|
+
MfaState,
|
|
106
|
+
Organization,
|
|
107
|
+
OrganizationProvider,
|
|
108
|
+
SessionInfo,
|
|
109
|
+
SessionProvider,
|
|
110
|
+
WebhookSummary,
|
|
111
|
+
} from './enterprise';
|
|
112
|
+
export { assertEnterpriseRequestContext, createEnterpriseRequestContext } from './enterprise';
|
|
89
113
|
export { setChatProvider, getChatProvider, setChatContext, getChatContext, setAgentProvider, getAgentProvider, registerApproval, resolveApproval, hasPendingApprovals, resetChatProvider } from './chatProvider.svelte';
|
|
90
114
|
export type { ChatProvider, ChatMessage, ChatContext, ChatAction, AgentProvider, AgentEvent, AgentOptions, AdminTool, AdminToolParameter, ToolResult } from './chatProvider.svelte';
|
|
91
115
|
export { getTheme, setTheme, toggleTheme, getResolvedTheme, getColorTheme, setColorTheme, getColorThemes, configureTheme, getThemeConfig, clearCssOverrides, builtinPresets, registerColorPreset, getColorPresets, registerThemeOwner, updateThemeOwner, unregisterThemeOwner, resetTheme } from './theme.svelte';
|
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 的编译期校验。 */
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type ParsedResourceAction = {
|
|
2
|
+
action: 'list' | 'create' | 'edit' | 'show' | 'clone';
|
|
3
|
+
id?: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export function parseResourceActionSegments(restSegments: string[]): ParsedResourceAction {
|
|
7
|
+
if (restSegments.length === 0) return { action: 'list' };
|
|
8
|
+
if (restSegments[0] === 'create') return { action: 'create' };
|
|
9
|
+
if (restSegments[0] === 'edit' && restSegments[1]) return { action: 'edit', id: restSegments[1] };
|
|
10
|
+
if (restSegments[0] === 'show' && restSegments[1]) return { action: 'show', id: restSegments[1] };
|
|
11
|
+
if (restSegments[0] === 'clone' && restSegments[1]) return { action: 'clone', id: restSegments[1] };
|
|
12
|
+
if (restSegments[1] === 'edit') return { action: 'edit', id: restSegments[0] };
|
|
13
|
+
return { action: 'show', id: restSegments[0] };
|
|
14
|
+
}
|
package/src/useParsed.svelte.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { registerRouterSync } from './router';
|
|
5
5
|
import { captureAdminContext } from './context.svelte';
|
|
6
|
+
import { parseResourceActionSegments } from './route-parsing';
|
|
6
7
|
|
|
7
8
|
interface ParsedRoute {
|
|
8
9
|
resource?: string;
|
|
@@ -107,47 +108,13 @@ export function useParsed(): ParsedRoute {
|
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
if (restSegments.length === 0) {
|
|
112
|
-
result.action = 'list';
|
|
113
|
-
} else if (restSegments[0] === 'create') {
|
|
114
|
-
result.action = 'create';
|
|
115
|
-
} else if (restSegments[0] === 'edit' && restSegments[1]) {
|
|
116
|
-
result.action = 'edit';
|
|
117
|
-
result.id = restSegments[1];
|
|
118
|
-
} else if (restSegments[0] === 'show' && restSegments[1]) {
|
|
119
|
-
result.action = 'show';
|
|
120
|
-
result.id = restSegments[1];
|
|
121
|
-
} else if (restSegments[0] === 'clone' && restSegments[1]) {
|
|
122
|
-
result.action = 'clone';
|
|
123
|
-
result.id = restSegments[1];
|
|
124
|
-
} else if (restSegments[0]) {
|
|
125
|
-
// Legacy: /:resource/:id
|
|
126
|
-
result.action = 'show';
|
|
127
|
-
result.id = restSegments[0];
|
|
128
|
-
}
|
|
111
|
+
Object.assign(result, parseResourceActionSegments(segments.slice(resourceIndex + 1)));
|
|
129
112
|
} else {
|
|
130
113
|
// Fallback if resource definitions are not loaded yet
|
|
131
114
|
result.resource = segments[0];
|
|
132
115
|
result.resourcePath = segments[0];
|
|
133
116
|
|
|
134
|
-
|
|
135
|
-
result.action = 'list';
|
|
136
|
-
} else if (segments[1] === 'create') {
|
|
137
|
-
result.action = 'create';
|
|
138
|
-
} else if (segments[1] === 'edit' && segments[2]) {
|
|
139
|
-
result.action = 'edit';
|
|
140
|
-
result.id = segments[2];
|
|
141
|
-
} else if (segments[1] === 'show' && segments[2]) {
|
|
142
|
-
result.action = 'show';
|
|
143
|
-
result.id = segments[2];
|
|
144
|
-
} else if (segments[1] === 'clone' && segments[2]) {
|
|
145
|
-
result.action = 'clone';
|
|
146
|
-
result.id = segments[2];
|
|
147
|
-
} else if (segments[1]) {
|
|
148
|
-
result.action = 'show';
|
|
149
|
-
result.id = segments[1];
|
|
150
|
-
}
|
|
117
|
+
Object.assign(result, parseResourceActionSegments(segments.slice(1)));
|
|
151
118
|
}
|
|
152
119
|
|
|
153
120
|
return result;
|