@h-ai/cli 1.0.0-beta.17 → 1.0.0-beta.19
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/README.en.md +6 -0
- package/README.ja.md +6 -0
- package/README.md +6 -0
- package/dist/{chunk-EWTIVFHN.js → chunk-2ZL5ORQ2.js} +94 -28
- package/dist/chunk-2ZL5ORQ2.js.map +1 -0
- package/dist/{chunk-S2ZP7BHG.js → chunk-L53P43B2.js} +138 -32
- package/dist/chunk-L53P43B2.js.map +1 -0
- package/dist/cli-config-templates-LWMD2I6V.js +5 -0
- package/dist/{cli-config-templates-MKVYSK3E.js.map → cli-config-templates-LWMD2I6V.js.map} +1 -1
- package/dist/{cli-skill-templates-M64VPXZ4.js → cli-skill-templates-D6S2OQ6X.js} +3 -3
- package/dist/{cli-skill-templates-M64VPXZ4.js.map → cli-skill-templates-D6S2OQ6X.js.map} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2797 -666
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -1
- package/package.json +22 -22
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -10
- package/templates/apps/admin/src/routes/+page.svelte.hbs +4 -4
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +17 -7
- package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +26 -6
- package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +2 -2
- package/templates/apps/api/apps/{{projectName}}-contract/src/app-schemas.ts.hbs +6 -1
- package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +13 -5
- package/templates/apps/api/apps/{{projectName}}-contract/tests/contract.test.ts.hbs +13 -5
- package/templates/apps/api/apps/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
- package/templates/apps/api/apps/{{projectName}}-serv/src/app.ts.hbs +71 -32
- package/templates/apps/api/apps/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/api/apps/{{projectName}}-serv/src/lib/server/init.ts.hbs +131 -54
- package/templates/apps/api/apps/{{projectName}}-serv/src/server/procedures/index.ts.hbs +39 -13
- package/templates/apps/api/apps/{{projectName}}-serv/tests/service.test.ts.hbs +78 -31
- package/templates/apps/api/apps/{{projectName}}-serv/tsup.config.ts.hbs +3 -1
- package/templates/apps/api/apps/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
- package/templates/apps/api/e2e/api-serv.spec.ts.hbs +20 -3
- package/templates/apps/api/e2e/capture-evidence.ts.hbs +4 -1
- package/templates/apps/api/eslint.config.js.hbs +3 -1
- package/templates/apps/api/images/serv.Dockerfile.hbs +3 -4
- package/templates/apps/api/playwright.config.ts.hbs +23 -19
- package/templates/apps/api/pnpm-workspace.yaml.hbs +27 -16
- package/templates/apps/fullstack/apps/{{projectName}}-app/capacitor.config.ts.hbs +1 -3
- package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +10 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +10 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/lib/capacitor.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +4 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +16 -4
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/App.svelte.hbs +8 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +4 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +16 -4
- package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +10 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/main.ts.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +4 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +19 -5
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +111 -26
- package/templates/apps/fullstack/e2e/session-close.spec.ts.hbs +19 -5
- package/templates/apps/fullstack/eslint.config.js.hbs +3 -1
- package/templates/apps/fullstack/images/serv.Dockerfile.hbs +3 -4
- package/templates/apps/fullstack/images/web.Dockerfile.hbs +0 -2
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +114 -23
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +57 -11
- package/templates/apps/fullstack/packages/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +65 -30
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +69 -21
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/business-settings.ts.hbs +21 -5
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +63 -11
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/app-procedures.ts.hbs +6 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +139 -49
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/security-roles.ts.hbs +43 -12
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +139 -28
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/runtime.test.ts.hbs +33 -10
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/security.test.ts.hbs +141 -25
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tsup.config.ts.hbs +6 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +9 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/feedback-client.ts.hbs +0 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/sensitive-actions.svelte.ts.hbs +15 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +33 -7
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/FeedbackEntry.svelte.hbs +16 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +104 -13
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/feedback.svelte.hbs +37 -7
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/roles.svelte.hbs +9 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +25 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +133 -19
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/security/role-label.ts.hbs +14 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/guide-store.ts.hbs +66 -34
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +132 -30
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/theme-store.svelte.ts.hbs +8 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +23 -5
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +143 -21
- package/templates/apps/fullstack/packages/{{projectName}}-shared/vitest.config.ts.hbs +7 -2
- package/templates/apps/fullstack/playwright.config.ts.hbs +9 -3
- package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +30 -15
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +57 -21
- package/templates/apps/h5/src/routes/+page.svelte.hbs +2 -2
- package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +2 -2
- package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +2 -2
- package/templates/apps/mobile-app/src/App.svelte.hbs +17 -5
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +14 -3
- package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +8 -1
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +6 -2
- package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +5 -1
- package/templates/apps/website/scripts/static-website.mjs.hbs +16 -3
- package/templates/apps/website/src/routes/+layout.svelte.hbs +44 -23
- package/templates/apps/website/src/routes/+page.svelte.hbs +4 -4
- package/templates/apps/website/src/routes/about/+page.svelte.hbs +2 -2
- package/templates/apps/website/src/routes/contact/+page.svelte.hbs +2 -2
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +20 -5
- package/templates/base/e2e/app.spec.ts.hbs +49 -16
- package/templates/base/eslint-rules.js +44 -25
- package/templates/base/eslint.config.js.hbs +3 -1
- package/templates/base/images/web.Dockerfile.hbs +3 -4
- package/templates/base/package.json.hbs +5 -4
- package/templates/base/patches/daisyui-5.7.50.patch +8 -0
- package/templates/base/patches/deepmerge-4.3.1.patch +51 -0
- package/templates/base/playwright.config.ts +3 -1
- package/templates/base/pnpm-workspace.yaml.hbs +33 -16
- package/templates/base/src/app.d.ts.hbs +1 -1
- package/templates/base/src/hooks.server.ts.hbs +36 -27
- package/templates/base/src/lib/build-metadata.ts.hbs +0 -1
- package/templates/base/src/lib/server/init.ts.hbs +124 -51
- package/templates/base/src/lib/server/kit-config.ts.hbs +7 -4
- package/templates/base/src/routes/+layout.server.ts.hbs +2 -2
- package/templates/base/svelte.config.js.hbs +10 -100
- package/templates/base/tests/scaffold.test.ts.hbs +12 -3
- package/templates/base/tsconfig.json.hbs +7 -4
- package/templates/base/vite.config.ts.hbs +108 -14
- package/templates/base/vitest.config.ts.hbs +6 -2
- package/templates/features/ai/routes/api/chat/+server.ts +3 -1
- package/templates/features/iam/routes-admin/(auth)/auth/forgot-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-admin/(auth)/auth/reset-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-h5/auth/forgot-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-h5/auth/reset-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-website/auth/forgot-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-website/auth/login/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-website/auth/register/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-website/auth/reset-password/+page.svelte.hbs +3 -3
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +11 -2
- package/templates/shared/browser-check/startup.js +32 -6
- package/templates/shared/browser-startup.spec.ts +3 -1
- package/templates/shared/build-metadata.ts +18 -4
- package/templates/shared/e2e-env.ts +22 -6
- package/templates/shared/native-name.mjs +9 -2
- package/templates/shared/public-base-path.mjs +17 -4
- package/templates/shared/run-e2e.mjs +40 -9
- package/templates/shared/run-node.mjs +15 -4
- package/templates/shared/run-tauri.mjs +37 -8
- package/templates/shared/vite-runtime-config.ts +9 -3
- package/templates/skills/AGENTS.md +10 -73
- package/templates/skills/bridges/admin/AGENTS.md +9 -65
- package/templates/skills/bridges/api/AGENTS.md +8 -59
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +9 -54
- package/templates/skills/bridges/h5/AGENTS.md +8 -63
- package/templates/skills/bridges/mobile-app/AGENTS.md +8 -62
- package/templates/skills/bridges/website/AGENTS.md +8 -63
- package/templates/skills/hai-ai/acp.md +9 -1
- package/templates/skills/hai-ai/guide.md +17 -6
- package/templates/skills/hai-ai/reference.md +21 -12
- package/templates/skills/hai-api-client/reference.md +1 -1
- package/templates/skills/hai-app-create/development.md +3 -1
- package/templates/skills/hai-app-create/project.md +33 -0
- package/templates/skills/hai-app-create/reference.md +8 -8
- package/templates/skills/hai-app-tests/reference.md +1 -1
- package/templates/skills/hai-build/SKILL.md +3 -2
- package/templates/skills/hai-build/paths.md +1 -1
- package/templates/skills/hai-build/reference.md +11 -7
- package/templates/skills/hai-capacitor/reference.md +2 -0
- package/templates/skills/hai-ci/SKILL.md +1 -0
- package/templates/skills/hai-core/reference.md +3 -0
- package/templates/skills/hai-crypto/reference.md +2 -0
- package/templates/skills/hai-deploy/SKILL.md +7 -1
- package/templates/skills/hai-deploy/middleware.md +3 -1
- package/templates/skills/hai-framework-sync/SKILL.md +13 -9
- package/templates/skills/hai-framework-sync/sync.md +2 -0
- package/templates/skills/hai-kit/reference.md +16 -4
- package/templates/skills/hai-payment/reference.md +2 -0
- package/templates/skills/hai-reach/SKILL.md +2 -1
- package/templates/skills/hai-reach/reference.md +1 -1
- package/templates/skills/hai-reldb/SKILL.md +1 -1
- package/templates/skills/hai-reldb/reference.md +4 -0
- package/templates/skills/hai-serv/reference.md +12 -2
- package/templates/skills/hai-storage/reference.md +12 -3
- package/templates/skills/hai-ui/SKILL.md +3 -2
- package/templates/skills/hai-ui/guide.md +1 -1
- package/templates/skills/hai-ui/reference.md +23 -35
- package/templates/skills/hai-vecdb/reference.md +1 -1
- package/dist/chunk-EWTIVFHN.js.map +0 -1
- package/dist/chunk-S2ZP7BHG.js.map +0 -1
- package/dist/cli-config-templates-MKVYSK3E.js +0 -5
|
@@ -88,7 +88,13 @@ export class ManagementStore {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
canOpenPage(page: string): boolean {
|
|
91
|
-
const permissions: Record<string, string> = {
|
|
91
|
+
const permissions: Record<string, string> = {
|
|
92
|
+
users: 'app.users.manage',
|
|
93
|
+
roles: 'app.roles.manage',
|
|
94
|
+
audit: 'audit:read',
|
|
95
|
+
configuration: 'app.config.manage',
|
|
96
|
+
feedback: 'feedback:read',
|
|
97
|
+
}
|
|
92
98
|
return !permissions[page] || this.hasPermission(permissions[page])
|
|
93
99
|
}
|
|
94
100
|
|
|
@@ -97,14 +103,21 @@ export class ManagementStore {
|
|
|
97
103
|
this.initializing = true
|
|
98
104
|
this.error = ''
|
|
99
105
|
try {
|
|
100
|
-
const init = await initializeManagementApi({
|
|
106
|
+
const init = await initializeManagementApi({
|
|
107
|
+
...options,
|
|
108
|
+
onSessionExpired: () => this.expireSession(),
|
|
109
|
+
})
|
|
101
110
|
if (!init.success) {
|
|
102
|
-
this.error = init.error.code === HaiApiClientError.CONFIG_ERROR.code
|
|
111
|
+
this.error = init.error.code === HaiApiClientError.CONFIG_ERROR.code
|
|
112
|
+
? m.api_not_configured()
|
|
113
|
+
: m.home_error()
|
|
103
114
|
return
|
|
104
115
|
}
|
|
105
116
|
const info = await managementApi.app.info()
|
|
106
117
|
if (!info.success) {
|
|
107
|
-
this.error = info.error.httpStatus === 400
|
|
118
|
+
this.error = info.error.httpStatus === 400
|
|
119
|
+
? info.error.message
|
|
120
|
+
: m.home_error()
|
|
108
121
|
return
|
|
109
122
|
}
|
|
110
123
|
this.info = info.data
|
|
@@ -122,7 +135,9 @@ export class ManagementStore {
|
|
|
122
135
|
await this.navigate(this.router.path.slice(1))
|
|
123
136
|
}
|
|
124
137
|
else if (me.error.httpStatus !== 401) {
|
|
125
|
-
this.error = me.error.httpStatus === 400
|
|
138
|
+
this.error = me.error.httpStatus === 400
|
|
139
|
+
? me.error.message
|
|
140
|
+
: m.operation_failed()
|
|
126
141
|
}
|
|
127
142
|
}
|
|
128
143
|
catch {
|
|
@@ -163,7 +178,9 @@ export class ManagementStore {
|
|
|
163
178
|
return
|
|
164
179
|
this.requestVersion++
|
|
165
180
|
this.loading = false
|
|
166
|
-
this.page = this.canOpenPage(page)
|
|
181
|
+
this.page = this.canOpenPage(page)
|
|
182
|
+
? page
|
|
183
|
+
: 'settings'
|
|
167
184
|
if (this.router.path !== `/${this.page}`)
|
|
168
185
|
this.router.navigate(`/${this.page}`)
|
|
169
186
|
this.page = this.router.path.slice(1)
|
|
@@ -191,18 +208,32 @@ export class ManagementStore {
|
|
|
191
208
|
const result = await this.request(() => requestSensitive('business.settings.update', options => managementApi.admin.configuration.update({ notice: this.businessNotice }, options)))
|
|
192
209
|
if (result?.success && this.info) {
|
|
193
210
|
this.info = { ...this.info, notice: result.data.notice }
|
|
194
|
-
toast.add({
|
|
211
|
+
toast.add({
|
|
212
|
+
'message': m.saved(),
|
|
213
|
+
'variant': 'success',
|
|
214
|
+
'data-testid': 'notice',
|
|
215
|
+
})
|
|
195
216
|
}
|
|
196
217
|
})
|
|
197
218
|
}
|
|
198
219
|
|
|
199
220
|
async queryUsers(): Promise<void> {
|
|
200
|
-
this.appliedUsersQuery = {
|
|
221
|
+
this.appliedUsersQuery = {
|
|
222
|
+
search: this.usersSearch.trim() || undefined,
|
|
223
|
+
accountStatus: this.usersStatus === 'all'
|
|
224
|
+
? undefined
|
|
225
|
+
: this.usersStatus,
|
|
226
|
+
}
|
|
201
227
|
await this.loadUsers()
|
|
202
228
|
}
|
|
203
229
|
|
|
204
230
|
async sortUsers(key: string, direction: 'asc' | 'desc'): Promise<void> {
|
|
205
|
-
const field = ([
|
|
231
|
+
const field = ([
|
|
232
|
+
'username',
|
|
233
|
+
'email',
|
|
234
|
+
'accountStatus',
|
|
235
|
+
'createdAt',
|
|
236
|
+
] as const).find(field => field === key)
|
|
206
237
|
if (!field)
|
|
207
238
|
return
|
|
208
239
|
this.usersSortBy = field
|
|
@@ -220,7 +251,10 @@ export class ManagementStore {
|
|
|
220
251
|
sortDirection: this.usersSortDirection,
|
|
221
252
|
}), version)
|
|
222
253
|
if (result?.success && version === this.requestVersion) {
|
|
223
|
-
this.users = result.data.items.map(user => ({
|
|
254
|
+
this.users = result.data.items.map(user => ({
|
|
255
|
+
...user,
|
|
256
|
+
avatarUrl: resolveAvatarUrl(user.avatarUrl),
|
|
257
|
+
}))
|
|
224
258
|
this.usersTotal = result.data.total
|
|
225
259
|
this.usersPage = result.data.page
|
|
226
260
|
}
|
|
@@ -281,14 +315,25 @@ export class ManagementStore {
|
|
|
281
315
|
severity: this.auditSeverity || undefined,
|
|
282
316
|
action: this.auditAction.trim() || undefined,
|
|
283
317
|
resource: this.auditResource.trim() || undefined,
|
|
284
|
-
startDate: this.auditStartDate
|
|
285
|
-
|
|
318
|
+
startDate: this.auditStartDate
|
|
319
|
+
? new SvelteDate(`${this.auditStartDate}T00:00:00`).toISOString()
|
|
320
|
+
: undefined,
|
|
321
|
+
endDate: this.auditEndDate
|
|
322
|
+
? new SvelteDate(`${this.auditEndDate}T23:59:59.999`).toISOString()
|
|
323
|
+
: undefined,
|
|
286
324
|
}
|
|
287
325
|
await this.loadAudit()
|
|
288
326
|
}
|
|
289
327
|
|
|
290
328
|
async sortAudit(key: string, direction: 'asc' | 'desc'): Promise<void> {
|
|
291
|
-
const field = ([
|
|
329
|
+
const field = ([
|
|
330
|
+
'createdAt',
|
|
331
|
+
'action',
|
|
332
|
+
'resource',
|
|
333
|
+
'category',
|
|
334
|
+
'outcome',
|
|
335
|
+
'severity',
|
|
336
|
+
] as const).find(field => field === key)
|
|
292
337
|
if (!field)
|
|
293
338
|
return
|
|
294
339
|
this.auditSortBy = field
|
|
@@ -326,7 +371,11 @@ export class ManagementStore {
|
|
|
326
371
|
}
|
|
327
372
|
this.auditBackupReceipt = result.data
|
|
328
373
|
this.auditBackupId = ''
|
|
329
|
-
toast.add({
|
|
374
|
+
toast.add({
|
|
375
|
+
'message': m.audit_backup_private_hint(),
|
|
376
|
+
'variant': 'success',
|
|
377
|
+
'data-testid': 'notice',
|
|
378
|
+
})
|
|
330
379
|
})
|
|
331
380
|
}
|
|
332
381
|
|
|
@@ -340,7 +389,11 @@ export class ManagementStore {
|
|
|
340
389
|
return
|
|
341
390
|
saved = true
|
|
342
391
|
await this.loadUsers(this.usersPage)
|
|
343
|
-
toast.add({
|
|
392
|
+
toast.add({
|
|
393
|
+
'message': m.saved(),
|
|
394
|
+
'variant': 'success',
|
|
395
|
+
'data-testid': 'notice',
|
|
396
|
+
})
|
|
344
397
|
})
|
|
345
398
|
return saved
|
|
346
399
|
}
|
|
@@ -356,7 +409,11 @@ export class ManagementStore {
|
|
|
356
409
|
return
|
|
357
410
|
}
|
|
358
411
|
invited = true
|
|
359
|
-
toast.add({
|
|
412
|
+
toast.add({
|
|
413
|
+
'message': m.saved(),
|
|
414
|
+
'variant': 'success',
|
|
415
|
+
'data-testid': 'notice',
|
|
416
|
+
})
|
|
360
417
|
})
|
|
361
418
|
return invited
|
|
362
419
|
}
|
|
@@ -368,9 +425,15 @@ export class ManagementStore {
|
|
|
368
425
|
if (!result?.success)
|
|
369
426
|
return
|
|
370
427
|
deleted = true
|
|
371
|
-
const page = this.users.length === 1
|
|
428
|
+
const page = this.users.length === 1
|
|
429
|
+
? Math.max(1, this.usersPage - 1)
|
|
430
|
+
: this.usersPage
|
|
372
431
|
await this.loadUsers(page)
|
|
373
|
-
toast.add({
|
|
432
|
+
toast.add({
|
|
433
|
+
'message': m.user_deleted(),
|
|
434
|
+
'variant': 'success',
|
|
435
|
+
'data-testid': 'notice',
|
|
436
|
+
})
|
|
374
437
|
})
|
|
375
438
|
return deleted
|
|
376
439
|
}
|
|
@@ -386,7 +449,11 @@ export class ManagementStore {
|
|
|
386
449
|
await managementApi.auth.clear()
|
|
387
450
|
this.clearSession()
|
|
388
451
|
}
|
|
389
|
-
toast.add({
|
|
452
|
+
toast.add({
|
|
453
|
+
'message': m.user_password_reset_done(),
|
|
454
|
+
'variant': 'success',
|
|
455
|
+
'data-testid': 'notice',
|
|
456
|
+
})
|
|
390
457
|
})
|
|
391
458
|
return reset
|
|
392
459
|
}
|
|
@@ -399,25 +466,38 @@ export class ManagementStore {
|
|
|
399
466
|
return
|
|
400
467
|
saved = true
|
|
401
468
|
await this.loadRoles()
|
|
402
|
-
toast.add({
|
|
469
|
+
toast.add({
|
|
470
|
+
'message': m.saved(),
|
|
471
|
+
'variant': 'success',
|
|
472
|
+
'data-testid': 'notice',
|
|
473
|
+
})
|
|
403
474
|
})
|
|
404
475
|
return saved
|
|
405
476
|
}
|
|
406
477
|
|
|
407
|
-
|
|
408
|
-
|
|
478
|
+
/** 提交资料表单;返回已确认的持久化结果供编辑器决定是否关闭。 */
|
|
479
|
+
async saveProfile(data: UserProfileSubmitData): Promise<boolean> {
|
|
480
|
+
return this.savePreferences({ displayName: data.displayName?.trim() ?? '', email: data.email?.trim() ?? '' })
|
|
409
481
|
}
|
|
410
482
|
|
|
411
|
-
|
|
483
|
+
/** 保存偏好并刷新当前用户;写入失败返回 false,不把失败当正常关闭。 */
|
|
484
|
+
async savePreferences(data: ProfileUpdate): Promise<boolean> {
|
|
485
|
+
let saved = false
|
|
412
486
|
await this.mutate(async () => {
|
|
413
487
|
const result = await this.request(() => requestSensitive('iam.profile.update', options => managementApi.profile.update(data, options)))
|
|
414
488
|
if (!result?.success)
|
|
415
489
|
return
|
|
490
|
+
saved = true
|
|
416
491
|
const me = await this.request(() => managementApi.iam.auth.currentUser())
|
|
417
492
|
if (me?.success)
|
|
418
493
|
this.setUser(me.data)
|
|
419
|
-
toast.add({
|
|
494
|
+
toast.add({
|
|
495
|
+
'message': m.saved(),
|
|
496
|
+
'variant': 'success',
|
|
497
|
+
'data-testid': 'notice',
|
|
498
|
+
})
|
|
420
499
|
})
|
|
500
|
+
return saved
|
|
421
501
|
}
|
|
422
502
|
|
|
423
503
|
async changePassword(data: ChangePasswordFormData): Promise<void> {
|
|
@@ -439,7 +519,11 @@ export class ManagementStore {
|
|
|
439
519
|
}
|
|
440
520
|
await managementApi.auth.clear()
|
|
441
521
|
this.clearSession()
|
|
442
|
-
toast.add({
|
|
522
|
+
toast.add({
|
|
523
|
+
'message': m.password_changed(),
|
|
524
|
+
'variant': 'success',
|
|
525
|
+
'data-testid': 'notice',
|
|
526
|
+
})
|
|
443
527
|
})
|
|
444
528
|
}
|
|
445
529
|
|
|
@@ -460,7 +544,11 @@ export class ManagementStore {
|
|
|
460
544
|
}
|
|
461
545
|
if (userId)
|
|
462
546
|
await this.loadUsers(this.usersPage)
|
|
463
|
-
toast.add({
|
|
547
|
+
toast.add({
|
|
548
|
+
'message': m.avatar_saved(),
|
|
549
|
+
'variant': 'success',
|
|
550
|
+
'data-testid': 'notice',
|
|
551
|
+
})
|
|
464
552
|
})
|
|
465
553
|
}
|
|
466
554
|
|
|
@@ -477,13 +565,19 @@ export class ManagementStore {
|
|
|
477
565
|
let binary = ''
|
|
478
566
|
for (const byte of bytes)
|
|
479
567
|
binary += String.fromCharCode(byte)
|
|
480
|
-
return {
|
|
568
|
+
return {
|
|
569
|
+
contentType: file.type as AvatarUpload['contentType'],
|
|
570
|
+
base64: btoa(binary),
|
|
571
|
+
}
|
|
481
572
|
}
|
|
482
573
|
|
|
483
574
|
private setUser(user: CurrentUser): void {
|
|
484
575
|
if (this.user?.id !== user.id)
|
|
485
576
|
this.sessionEpoch++
|
|
486
|
-
this.user = {
|
|
577
|
+
this.user = {
|
|
578
|
+
...user,
|
|
579
|
+
avatarUrl: resolveAvatarUrl(user.avatarUrl),
|
|
580
|
+
}
|
|
487
581
|
if (!this.hasPermission('app.users.manage')) {
|
|
488
582
|
this.users = []
|
|
489
583
|
this.usersTotal = 0
|
|
@@ -504,7 +598,9 @@ export class ManagementStore {
|
|
|
504
598
|
this.auditBackupReceipt = null
|
|
505
599
|
this.auditBackupId = ''
|
|
506
600
|
}
|
|
507
|
-
this.applyLocale(typeof user.metadata?.locale === 'string'
|
|
601
|
+
this.applyLocale(typeof user.metadata?.locale === 'string'
|
|
602
|
+
? user.metadata.locale
|
|
603
|
+
: this.info?.defaultLocale ?? getLocale())
|
|
508
604
|
if (typeof user.metadata?.theme === 'string' && AVAILABLE_THEMES.includes(user.metadata.theme))
|
|
509
605
|
themeStore.set(user.metadata.theme)
|
|
510
606
|
else
|
|
@@ -574,7 +670,13 @@ export class ManagementStore {
|
|
|
574
670
|
this.error = m.forbidden()
|
|
575
671
|
}
|
|
576
672
|
else {
|
|
577
|
-
this.error = result.error.httpStatus === 403
|
|
673
|
+
this.error = result.error.httpStatus === 403
|
|
674
|
+
? m.forbidden()
|
|
675
|
+
: result.error.httpStatus === 409
|
|
676
|
+
? m.conflict()
|
|
677
|
+
: result.error.httpStatus === 400 || result.error.httpStatus === 422
|
|
678
|
+
? result.error.message || m.validation_failed()
|
|
679
|
+
: m.operation_failed()
|
|
578
680
|
}
|
|
579
681
|
this.error = core.error.formatMessage({ message: this.error, traceId: result.error.traceId })
|
|
580
682
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { applyTheme, getSavedTheme } from '@h-ai/ui/theme'
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_THEME = '{{fullstack.theme.defaultTheme}}'
|
|
4
|
-
export const AVAILABLE_THEMES: readonly string[] = [
|
|
4
|
+
export const AVAILABLE_THEMES: readonly string[] = [
|
|
5
|
+
{{#each fullstack.theme.themes}}
|
|
6
|
+
'{{this}}',
|
|
7
|
+
{{/each}}
|
|
8
|
+
]
|
|
5
9
|
|
|
6
10
|
/** 只维护响应式状态和已编译主题白名单,DOM 与安全持久化复用 UI 公共工具。 */
|
|
7
11
|
class ThemeState {
|
|
@@ -9,7 +13,9 @@ class ThemeState {
|
|
|
9
13
|
|
|
10
14
|
constructor() {
|
|
11
15
|
const saved = getSavedTheme(DEFAULT_THEME)
|
|
12
|
-
this.set(AVAILABLE_THEMES.includes(saved)
|
|
16
|
+
this.set(AVAILABLE_THEMES.includes(saved)
|
|
17
|
+
? saved
|
|
18
|
+
: DEFAULT_THEME)
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
set(value: string): void {
|
package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs
CHANGED
|
@@ -18,7 +18,13 @@ function createServerFetch() {
|
|
|
18
18
|
const key = crypto.asymmetric.generateKeyPair()
|
|
19
19
|
if (!key.success)
|
|
20
20
|
throw new Error(key.error.message)
|
|
21
|
-
const created = crypto.transport.createServer({
|
|
21
|
+
const created = crypto.transport.createServer({
|
|
22
|
+
serverKeyRing: {
|
|
23
|
+
activeKeyId: 'test-v1',
|
|
24
|
+
keys: { 'test-v1': key.data },
|
|
25
|
+
},
|
|
26
|
+
keyStore: createInMemoryKeyStore(),
|
|
27
|
+
})
|
|
22
28
|
if (!created.success)
|
|
23
29
|
throw new Error(created.error.message)
|
|
24
30
|
const server = created.data
|
|
@@ -31,7 +37,11 @@ function createServerFetch() {
|
|
|
31
37
|
if (url.pathname.endsWith('/_hai/key-exchange')) {
|
|
32
38
|
const body = await request.json() as { clientPublicKey: string }
|
|
33
39
|
const clientId = await server.registerClientKey(body.clientPublicKey)
|
|
34
|
-
return Response.json({
|
|
40
|
+
return Response.json({
|
|
41
|
+
serverKeyId: server.getServerKeyId(),
|
|
42
|
+
serverPublicKey: server.getServerPublicKey(),
|
|
43
|
+
clientId,
|
|
44
|
+
})
|
|
35
45
|
}
|
|
36
46
|
const clientId = request.headers.get(TRANSPORT_PROTOCOL.CLIENT_ID_HEADER)
|
|
37
47
|
if (!clientId)
|
|
@@ -49,7 +59,9 @@ function createServerFetch() {
|
|
|
49
59
|
const response = await server.encryptResponse(verified.data.context, JSON.stringify({ success: true, data }), 200)
|
|
50
60
|
if (!response.success)
|
|
51
61
|
throw new Error(response.error.message)
|
|
52
|
-
return Response.json(response.data, {
|
|
62
|
+
return Response.json(response.data, {
|
|
63
|
+
headers: { [TRANSPORT_PROTOCOL.ENCRYPTED_HEADER]: TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE },
|
|
64
|
+
})
|
|
53
65
|
}
|
|
54
66
|
return { fetchFn, requests, messages }
|
|
55
67
|
}
|
|
@@ -75,7 +87,9 @@ describe('shared API transport', () => {
|
|
|
75
87
|
})
|
|
76
88
|
|
|
77
89
|
it.each([false, true])('uses the injected API origin when native configuration is required: %s', async (requireApiBase) => {
|
|
78
|
-
Object.assign(globalThis, {
|
|
90
|
+
Object.assign(globalThis, {
|
|
91
|
+
__HAI_PUBLIC_CONFIG__: { apiBase: 'https://runtime.example.com' },
|
|
92
|
+
})
|
|
79
93
|
const server = createServerFetch()
|
|
80
94
|
try {
|
|
81
95
|
expect((await initializeManagementApi({ fetchFn: server.fetchFn, requireApiBase })).success).toBe(true)
|
|
@@ -89,7 +103,11 @@ describe('shared API transport', () => {
|
|
|
89
103
|
|
|
90
104
|
it('encrypts business input and preserves the native token transport header', async () => {
|
|
91
105
|
const server = createServerFetch()
|
|
92
|
-
const init = await initializeManagementApi({
|
|
106
|
+
const init = await initializeManagementApi({
|
|
107
|
+
fetchFn: server.fetchFn,
|
|
108
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
109
|
+
headers: { 'X-HAI-Token-Transport': 'body' },
|
|
110
|
+
})
|
|
93
111
|
expect(init.success).toBe(true)
|
|
94
112
|
try {
|
|
95
113
|
const result = await managementApi.app.echo({ message: 'private message' })
|
package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs
CHANGED
|
@@ -6,10 +6,29 @@ import { ManagementStore } from '../src/lib/stores/management-store.svelte.js'
|
|
|
6
6
|
|
|
7
7
|
const api = vi.hoisted(() => ({
|
|
8
8
|
initialize: vi.fn(),
|
|
9
|
-
app: {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
app: {
|
|
10
|
+
info: vi.fn(),
|
|
11
|
+
},
|
|
12
|
+
auth: {
|
|
13
|
+
clear: vi.fn(),
|
|
14
|
+
},
|
|
15
|
+
iam: {
|
|
16
|
+
auth: {
|
|
17
|
+
logout: vi.fn(),
|
|
18
|
+
currentUser: vi.fn(),
|
|
19
|
+
reauthenticate: vi.fn(),
|
|
20
|
+
changePassword: vi.fn(),
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
admin: {
|
|
24
|
+
users: {
|
|
25
|
+
list: vi.fn(),
|
|
26
|
+
resetPassword: vi.fn(),
|
|
27
|
+
},
|
|
28
|
+
audit: {
|
|
29
|
+
list: vi.fn(),
|
|
30
|
+
},
|
|
31
|
+
},
|
|
13
32
|
}))
|
|
14
33
|
vi.mock('../src/lib/api/api-client.js', () => ({
|
|
15
34
|
managementApi: api,
|
|
@@ -27,8 +46,17 @@ const member = {
|
|
|
27
46
|
createdAt: '2026-01-01T00:00:00.000Z',
|
|
28
47
|
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
29
48
|
}
|
|
30
|
-
const administrator = {
|
|
31
|
-
|
|
49
|
+
const administrator = {
|
|
50
|
+
...member,
|
|
51
|
+
id: 'admin-id',
|
|
52
|
+
username: 'admin',
|
|
53
|
+
roles: ['admin'],
|
|
54
|
+
permissions: ['app.users.manage', 'app.roles.manage'],
|
|
55
|
+
}
|
|
56
|
+
const forbidden = {
|
|
57
|
+
success: false,
|
|
58
|
+
error: { code: 'test:forbidden', message: 'Forbidden', httpStatus: 403 },
|
|
59
|
+
} as const
|
|
32
60
|
|
|
33
61
|
function deferred<T>() {
|
|
34
62
|
let resolve!: (result: T) => void
|
|
@@ -41,7 +69,10 @@ function deferred<T>() {
|
|
|
41
69
|
beforeEach(() => {
|
|
42
70
|
vi.resetAllMocks()
|
|
43
71
|
vi.stubGlobal('location', { hash: '#/users' })
|
|
44
|
-
vi.stubGlobal('history', {
|
|
72
|
+
vi.stubGlobal('history', {
|
|
73
|
+
replaceState: vi.fn(),
|
|
74
|
+
pushState: vi.fn(),
|
|
75
|
+
})
|
|
45
76
|
api.iam.auth.logout.mockResolvedValue({ success: true })
|
|
46
77
|
api.iam.auth.currentUser.mockResolvedValue({ success: true, data: member })
|
|
47
78
|
})
|
|
@@ -66,7 +97,19 @@ describe('management session and request ownership', () => {
|
|
|
66
97
|
})
|
|
67
98
|
|
|
68
99
|
it.each(['hai:iam:002', 'hai:iam:034', 'hai:iam:999'])('password failures use safe messages and keep the active session: %s', async (code) => {
|
|
69
|
-
api.iam.auth.changePassword.mockResolvedValueOnce({
|
|
100
|
+
api.iam.auth.changePassword.mockResolvedValueOnce({
|
|
101
|
+
success: false,
|
|
102
|
+
error: {
|
|
103
|
+
code,
|
|
104
|
+
message: 'Private password or database detail',
|
|
105
|
+
httpStatus: code === 'hai:iam:002'
|
|
106
|
+
? 401
|
|
107
|
+
: code === 'hai:iam:999'
|
|
108
|
+
? 500
|
|
109
|
+
: 400,
|
|
110
|
+
traceId: 'password-failure',
|
|
111
|
+
},
|
|
112
|
+
})
|
|
70
113
|
const store = createStore()
|
|
71
114
|
store.user = member
|
|
72
115
|
await store.changePassword({ oldPassword: 'CurrentPassword123!', newPassword: 'NewPassword123!', confirmPassword: 'NewPassword123!' })
|
|
@@ -88,13 +131,22 @@ describe('management session and request ownership', () => {
|
|
|
88
131
|
})
|
|
89
132
|
|
|
90
133
|
it.each(['dormant', 'closed'] as const)('resetting another duty password preserves its %s state and the administrator session', async (accountStatus) => {
|
|
91
|
-
const target: AdminUser = {
|
|
134
|
+
const target: AdminUser = {
|
|
135
|
+
...member,
|
|
136
|
+
roles: ['config_admin'],
|
|
137
|
+
accountStatus,
|
|
138
|
+
accountType: 'permanent',
|
|
139
|
+
accountExpiresAt: null,
|
|
140
|
+
lastLoginAt: null,
|
|
141
|
+
}
|
|
92
142
|
api.admin.users.resetPassword.mockResolvedValueOnce({ success: true })
|
|
93
143
|
const store = createStore()
|
|
94
144
|
store.user = administrator
|
|
95
145
|
store.users = [target]
|
|
96
146
|
expect(await store.resetUserPassword(target, 'NewPassword123!')).toBe(true)
|
|
97
|
-
expect(api.admin.users.resetPassword).toHaveBeenCalledWith({ id: target.id, newPassword: 'NewPassword123!' }, expect.objectContaining({
|
|
147
|
+
expect(api.admin.users.resetPassword).toHaveBeenCalledWith({ id: target.id, newPassword: 'NewPassword123!' }, expect.objectContaining({
|
|
148
|
+
context: expect.any(Object),
|
|
149
|
+
}))
|
|
98
150
|
expect(store.users[0]?.accountStatus).toBe(accountStatus)
|
|
99
151
|
expect(store.user?.id).toBe(administrator.id)
|
|
100
152
|
expect(api.auth.clear).not.toHaveBeenCalled()
|
|
@@ -102,8 +154,22 @@ describe('management session and request ownership', () => {
|
|
|
102
154
|
})
|
|
103
155
|
|
|
104
156
|
it('an unsuccessful self reset retains authentication and hides private server details', async () => {
|
|
105
|
-
const target: AdminUser = {
|
|
106
|
-
|
|
157
|
+
const target: AdminUser = {
|
|
158
|
+
...administrator,
|
|
159
|
+
roles: ['admin'],
|
|
160
|
+
accountType: 'permanent',
|
|
161
|
+
accountExpiresAt: null,
|
|
162
|
+
lastLoginAt: null,
|
|
163
|
+
}
|
|
164
|
+
api.admin.users.resetPassword.mockResolvedValueOnce({
|
|
165
|
+
success: false,
|
|
166
|
+
error: {
|
|
167
|
+
code: 'hai:iam:999',
|
|
168
|
+
httpStatus: 500,
|
|
169
|
+
message: 'Private password reset detail',
|
|
170
|
+
traceId: 'reset-failure',
|
|
171
|
+
},
|
|
172
|
+
})
|
|
107
173
|
const store = createStore()
|
|
108
174
|
store.user = administrator
|
|
109
175
|
expect(await store.resetUserPassword(target, 'NewPassword123!')).toBe(false)
|
|
@@ -114,7 +180,13 @@ describe('management session and request ownership', () => {
|
|
|
114
180
|
})
|
|
115
181
|
|
|
116
182
|
it('a successful self reset clears local authentication after the server confirms revocation', async () => {
|
|
117
|
-
const target: AdminUser = {
|
|
183
|
+
const target: AdminUser = {
|
|
184
|
+
...administrator,
|
|
185
|
+
roles: ['admin'],
|
|
186
|
+
accountType: 'permanent',
|
|
187
|
+
accountExpiresAt: null,
|
|
188
|
+
lastLoginAt: null,
|
|
189
|
+
}
|
|
118
190
|
const response = deferred<{ success: true }>()
|
|
119
191
|
api.admin.users.resetPassword.mockReturnValueOnce(response.promise)
|
|
120
192
|
const store = createStore()
|
|
@@ -131,7 +203,15 @@ describe('management session and request ownership', () => {
|
|
|
131
203
|
it('preserves the clock synchronization advice when application bootstrap is rejected', async () => {
|
|
132
204
|
const message = 'Synchronize device and server clocks, then refresh to retry.'
|
|
133
205
|
api.initialize.mockResolvedValueOnce({ success: true })
|
|
134
|
-
api.app.info.mockResolvedValueOnce({
|
|
206
|
+
api.app.info.mockResolvedValueOnce({
|
|
207
|
+
success: false,
|
|
208
|
+
error: {
|
|
209
|
+
code: 'hai:api-client:007',
|
|
210
|
+
httpStatus: 400,
|
|
211
|
+
message,
|
|
212
|
+
cause: { transportCode: 'hai:crypto:071' },
|
|
213
|
+
},
|
|
214
|
+
})
|
|
135
215
|
const store = createStore()
|
|
136
216
|
await store.initialize({ baseUrl: 'https://api.example.test' })
|
|
137
217
|
expect(store.error).toBe(message)
|
|
@@ -141,7 +221,10 @@ describe('management session and request ownership', () => {
|
|
|
141
221
|
|
|
142
222
|
it('preserves useful validation advice instead of replacing it with a generic operation error', async () => {
|
|
143
223
|
const message = 'Synchronize device and server clocks, then refresh to retry.'
|
|
144
|
-
api.admin.users.list.mockResolvedValueOnce({
|
|
224
|
+
api.admin.users.list.mockResolvedValueOnce({
|
|
225
|
+
success: false,
|
|
226
|
+
error: { code: 'hai:api-client:007', httpStatus: 400, message },
|
|
227
|
+
})
|
|
145
228
|
const store = createStore()
|
|
146
229
|
store.user = administrator
|
|
147
230
|
await store.loadUsers()
|
|
@@ -185,7 +268,14 @@ describe('management session and request ownership', () => {
|
|
|
185
268
|
api.admin.users.list.mockResolvedValueOnce(forbidden)
|
|
186
269
|
const store = createStore()
|
|
187
270
|
store.user = administrator
|
|
188
|
-
store.roles = [
|
|
271
|
+
store.roles = [
|
|
272
|
+
{
|
|
273
|
+
id: 'role-id',
|
|
274
|
+
code: 'admin',
|
|
275
|
+
name: 'Administrator',
|
|
276
|
+
userCount: 1,
|
|
277
|
+
},
|
|
278
|
+
]
|
|
189
279
|
await store.loadUsers()
|
|
190
280
|
expect(store.user?.roles).toEqual(['user'])
|
|
191
281
|
expect(store.page).toBe('settings')
|
|
@@ -197,7 +287,10 @@ describe('management session and request ownership', () => {
|
|
|
197
287
|
})
|
|
198
288
|
|
|
199
289
|
it.each(['hai:iam:105', 'hai:common:100'])('keeps the page, original user and unsaved fields when an expired session request is cancelled: %s', async (code) => {
|
|
200
|
-
api.admin.users.list.mockResolvedValueOnce({
|
|
290
|
+
api.admin.users.list.mockResolvedValueOnce({
|
|
291
|
+
success: false,
|
|
292
|
+
error: { code, message: 'Expired', httpStatus: 401 },
|
|
293
|
+
})
|
|
201
294
|
const store = createStore()
|
|
202
295
|
store.user = administrator
|
|
203
296
|
store.businessNotice = 'Unsaved business input'
|
|
@@ -221,7 +314,15 @@ describe('management session and request ownership', () => {
|
|
|
221
314
|
})
|
|
222
315
|
|
|
223
316
|
it('user paging and header sorting keep applied filters until Query submits drafts', async () => {
|
|
224
|
-
api.admin.users.list.mockResolvedValue({
|
|
317
|
+
api.admin.users.list.mockResolvedValue({
|
|
318
|
+
success: true,
|
|
319
|
+
data: {
|
|
320
|
+
items: [],
|
|
321
|
+
total: 60,
|
|
322
|
+
page: 1,
|
|
323
|
+
pageSize: 20,
|
|
324
|
+
},
|
|
325
|
+
})
|
|
225
326
|
const store = createStore()
|
|
226
327
|
store.user = administrator
|
|
227
328
|
store.usersSearch = 'applied'
|
|
@@ -232,13 +333,29 @@ it('user paging and header sorting keep applied filters until Query submits draf
|
|
|
232
333
|
await store.loadUsers(2)
|
|
233
334
|
expect(api.admin.users.list).toHaveBeenLastCalledWith(expect.objectContaining({ page: 2, search: 'applied', accountStatus: 'active' }))
|
|
234
335
|
await store.sortUsers('username', 'asc')
|
|
235
|
-
expect(api.admin.users.list).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
336
|
+
expect(api.admin.users.list).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
337
|
+
page: 1,
|
|
338
|
+
search: 'applied',
|
|
339
|
+
accountStatus: 'active',
|
|
340
|
+
sortBy: 'username',
|
|
341
|
+
sortDirection: 'asc',
|
|
342
|
+
}))
|
|
236
343
|
await store.queryUsers()
|
|
237
344
|
expect(api.admin.users.list).toHaveBeenLastCalledWith(expect.objectContaining({ search: 'draft', accountStatus: 'dormant' }))
|
|
238
345
|
})
|
|
239
346
|
|
|
240
347
|
it('audit paging and header sorting do not submit unfinished filter drafts', async () => {
|
|
241
|
-
api.admin.audit.list.mockResolvedValue({
|
|
348
|
+
api.admin.audit.list.mockResolvedValue({
|
|
349
|
+
success: true,
|
|
350
|
+
data: {
|
|
351
|
+
items: [],
|
|
352
|
+
stats: [],
|
|
353
|
+
backupEnabled: false,
|
|
354
|
+
total: 60,
|
|
355
|
+
page: 1,
|
|
356
|
+
pageSize: 20,
|
|
357
|
+
},
|
|
358
|
+
})
|
|
242
359
|
const store = createStore()
|
|
243
360
|
store.user = administrator
|
|
244
361
|
store.auditResource = 'applied'
|
|
@@ -247,7 +364,12 @@ it('audit paging and header sorting do not submit unfinished filter drafts', asy
|
|
|
247
364
|
await store.loadAudit(2)
|
|
248
365
|
expect(api.admin.audit.list).toHaveBeenLastCalledWith(expect.objectContaining({ page: 2, resource: 'applied' }))
|
|
249
366
|
await store.sortAudit('action', 'asc')
|
|
250
|
-
expect(api.admin.audit.list).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
367
|
+
expect(api.admin.audit.list).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
368
|
+
page: 1,
|
|
369
|
+
resource: 'applied',
|
|
370
|
+
sortBy: 'action',
|
|
371
|
+
sortDirection: 'asc',
|
|
372
|
+
}))
|
|
251
373
|
await store.queryAudit()
|
|
252
374
|
expect(api.admin.audit.list).toHaveBeenLastCalledWith(expect.objectContaining({ page: 1, resource: 'draft' }))
|
|
253
375
|
})
|