@h-ai/cli 1.0.0-beta.18 → 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 +4 -0
- package/README.ja.md +4 -0
- package/README.md +4 -0
- package/dist/{chunk-6YZBC5MO.js → chunk-2ZL5ORQ2.js} +85 -25
- package/dist/chunk-2ZL5ORQ2.js.map +1 -0
- package/dist/{chunk-7NVRYYHN.js → chunk-L53P43B2.js} +129 -31
- package/dist/chunk-L53P43B2.js.map +1 -0
- package/dist/cli-config-templates-LWMD2I6V.js +5 -0
- package/dist/{cli-config-templates-QG5HOYR5.js.map → cli-config-templates-LWMD2I6V.js.map} +1 -1
- package/dist/{cli-skill-templates-W4I6ZKYW.js → cli-skill-templates-D6S2OQ6X.js} +3 -3
- package/dist/{cli-skill-templates-W4I6ZKYW.js.map → cli-skill-templates-D6S2OQ6X.js.map} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2747 -671
- 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 +2 -0
- 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 +2 -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/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-6YZBC5MO.js.map +0 -1
- package/dist/chunk-7NVRYYHN.js.map +0 -1
- package/dist/cli-config-templates-QG5HOYR5.js +0 -5
|
@@ -15,7 +15,6 @@ import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
|
|
15
15
|
import { closeModules, initializeModules } from '../src/server/runtime.js'
|
|
16
16
|
|
|
17
17
|
let storageRoot: string
|
|
18
|
-
|
|
19
18
|
beforeEach(async () => {
|
|
20
19
|
storageRoot = await mkdtemp(path.join(os.tmpdir(), '{{projectName}}-bootstrap-'))
|
|
21
20
|
vi.stubEnv('HAI_DB_DATABASE', '":memory:"')
|
|
@@ -23,22 +22,33 @@ beforeEach(async () => {
|
|
|
23
22
|
vi.stubEnv('HAI_IAM_PASSWORD_REQUIREUPPERCASE', 'true')
|
|
24
23
|
core.init({ configDir: './config' })
|
|
25
24
|
})
|
|
26
|
-
|
|
27
25
|
afterEach(async () => {
|
|
28
26
|
await closeModules()
|
|
29
27
|
vi.unstubAllEnvs()
|
|
30
28
|
await rm(storageRoot, { recursive: true, force: true })
|
|
31
29
|
})
|
|
32
|
-
|
|
33
30
|
it.each([
|
|
34
31
|
{ password: 'abc' },
|
|
35
32
|
{ password: 'lowercase123!' },
|
|
36
33
|
])('startup explains the failed password policy and permits a clean retry: $password', async ({ password }) => {
|
|
37
34
|
vi.stubEnv('HAI_ADMIN_DEFAULT_PASSWORD', password)
|
|
38
|
-
await expect(initializeModules()).rejects.toMatchObject({
|
|
39
|
-
|
|
35
|
+
await expect(initializeModules()).rejects.toMatchObject({
|
|
36
|
+
cause: {
|
|
37
|
+
ext: {
|
|
38
|
+
module: 'bootstrap',
|
|
39
|
+
fields: ['HAI_ADMIN_DEFAULT_PASSWORD'],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
for (const module of [
|
|
44
|
+
audit,
|
|
45
|
+
iam,
|
|
46
|
+
storage,
|
|
47
|
+
crypto,
|
|
48
|
+
cache,
|
|
49
|
+
reldb,
|
|
50
|
+
])
|
|
40
51
|
expect(module.isInitialized).toBe(false)
|
|
41
|
-
|
|
42
52
|
// 失败后再次初始化,确认未保留半成品管理员或未释放的模块。
|
|
43
53
|
vi.stubEnv('HAI_ADMIN_DEFAULT_PASSWORD', 'Bootstrap-retry-Admin123!')
|
|
44
54
|
await initializeModules()
|
|
@@ -49,14 +59,27 @@ it.each([
|
|
|
49
59
|
expect(users.data.items[0]?.username).toBe('admin')
|
|
50
60
|
}
|
|
51
61
|
})
|
|
52
|
-
|
|
53
62
|
it.each(['', ' '])('startup identifies an unset administrator password without exposing its value', async (password) => {
|
|
54
63
|
vi.stubEnv('HAI_ADMIN_DEFAULT_PASSWORD', password)
|
|
55
|
-
await expect(initializeModules()).rejects.toMatchObject({
|
|
56
|
-
|
|
64
|
+
await expect(initializeModules()).rejects.toMatchObject({
|
|
65
|
+
cause: {
|
|
66
|
+
ext: {
|
|
67
|
+
module: 'bootstrap',
|
|
68
|
+
code: HaiConfigError.CONFIG_ENV_VAR_MISSING.code,
|
|
69
|
+
fields: ['HAI_ADMIN_DEFAULT_PASSWORD'],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
for (const module of [
|
|
74
|
+
audit,
|
|
75
|
+
iam,
|
|
76
|
+
storage,
|
|
77
|
+
crypto,
|
|
78
|
+
cache,
|
|
79
|
+
reldb,
|
|
80
|
+
])
|
|
57
81
|
expect(module.isInitialized).toBe(false)
|
|
58
82
|
})
|
|
59
|
-
|
|
60
83
|
{{#if features.feedback}}
|
|
61
84
|
it('optional feedback failure does not roll back authentication and business modules', async () => {
|
|
62
85
|
vi.stubEnv('HAI_ADMIN_DEFAULT_PASSWORD', 'Bootstrap-retry-Admin123!')
|
|
@@ -29,11 +29,23 @@ beforeAll(async () => {
|
|
|
29
29
|
expect((await reldb.init({ type: 'sqlite', database: ':memory:' })).success).toBe(true)
|
|
30
30
|
expect((await cache.init({ type: 'memory' })).success).toBe(true)
|
|
31
31
|
expect((await storage.init({ type: 'local', root: directory })).success).toBe(true)
|
|
32
|
-
expect((await audit.init({
|
|
32
|
+
expect((await audit.init({
|
|
33
|
+
storage,
|
|
34
|
+
backup: { enabled: true },
|
|
35
|
+
})).success).toBe(true)
|
|
33
36
|
expect((await iam.init({
|
|
34
37
|
initialCredentials: { enabled: true },
|
|
35
38
|
passwordReset: { enabled: true, mode: 'email' },
|
|
36
|
-
reauthentication: {
|
|
39
|
+
reauthentication: {
|
|
40
|
+
enabled: true,
|
|
41
|
+
operations: [
|
|
42
|
+
'iam.users.create',
|
|
43
|
+
'iam.users.invite',
|
|
44
|
+
'iam.users.resetPassword',
|
|
45
|
+
'audit.backup',
|
|
46
|
+
'business.settings.update',
|
|
47
|
+
],
|
|
48
|
+
},
|
|
37
49
|
onPasswordResetRequest: async (user, token) => {
|
|
38
50
|
invitations.set(user.username, token)
|
|
39
51
|
if (deliveryUnknown)
|
|
@@ -56,7 +68,17 @@ beforeAll(async () => {
|
|
|
56
68
|
}
|
|
57
69
|
const app = serv.createApp({
|
|
58
70
|
contract: {{fullstack.contractExportName}},
|
|
59
|
-
procedures: createServerProcedures({
|
|
71
|
+
procedures: createServerProcedures({
|
|
72
|
+
app: {
|
|
73
|
+
name: 'Security fixture',
|
|
74
|
+
version: 'test',
|
|
75
|
+
env: 'test',
|
|
76
|
+
defaultLocale: 'en-US',
|
|
77
|
+
supportedLocales: ['en-US'],
|
|
78
|
+
startedAt: new Date().toISOString(),
|
|
79
|
+
frontends: ['web'],
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
60
82
|
iam,
|
|
61
83
|
http: { openapi: false, docs: false },
|
|
62
84
|
})
|
|
@@ -100,23 +122,57 @@ async function proof(code: Duty, operation: string) {
|
|
|
100
122
|
}
|
|
101
123
|
|
|
102
124
|
function post(code: Duty, route: string, input: object = {}, ticket?: string) {
|
|
103
|
-
return fetch(`${base}${route}`, {
|
|
125
|
+
return fetch(`${base}${route}`, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: {
|
|
128
|
+
'content-type': 'application/json',
|
|
129
|
+
'authorization': `Bearer ${actor(code).token}`,
|
|
130
|
+
...(ticket
|
|
131
|
+
? { 'x-hai-reauthentication': ticket }
|
|
132
|
+
: {}),
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify(input),
|
|
135
|
+
})
|
|
104
136
|
}
|
|
105
137
|
|
|
106
138
|
it('系统、审计、配置与业务职责的直接 HTTP 请求各有边界', async () => {
|
|
107
|
-
expect(await (await post('audit_admin', '/admin/users/list')).json()).toMatchObject({
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
139
|
+
expect(await (await post('audit_admin', '/admin/users/list')).json()).toMatchObject({
|
|
140
|
+
success: false,
|
|
141
|
+
error: { httpStatus: 403 },
|
|
142
|
+
})
|
|
143
|
+
expect(await (await post('admin', '/admin/audit/list')).json()).toMatchObject({
|
|
144
|
+
success: false,
|
|
145
|
+
error: { httpStatus: 403 },
|
|
146
|
+
})
|
|
147
|
+
expect(await (await post('admin', '/admin/configuration/update', { notice: 'Rejected' })).json()).toMatchObject({
|
|
148
|
+
success: false,
|
|
149
|
+
error: { httpStatus: 403 },
|
|
150
|
+
})
|
|
151
|
+
expect(await (await post('business_operator', '/admin/users/list')).json()).toMatchObject({
|
|
152
|
+
success: false,
|
|
153
|
+
error: { httpStatus: 403 },
|
|
154
|
+
})
|
|
111
155
|
const ticket = await proof('config_admin', 'business.settings.update')
|
|
112
156
|
expect(await (await post('config_admin', '/admin/configuration/update', { notice: 'Authorized public notice' }, ticket)).json()).toMatchObject({ success: true })
|
|
113
157
|
const info = await fetch(`${base}/app/info`, { method: 'POST' })
|
|
114
|
-
expect(await info.json()).toMatchObject({
|
|
158
|
+
expect(await info.json()).toMatchObject({
|
|
159
|
+
success: true,
|
|
160
|
+
data: { notice: 'Authorized public notice' },
|
|
161
|
+
})
|
|
115
162
|
})
|
|
116
163
|
|
|
117
164
|
it('再鉴别按用途单次使用,临时账号状态变化立即阻止旧会话', async () => {
|
|
118
|
-
const input = {
|
|
119
|
-
|
|
165
|
+
const input = {
|
|
166
|
+
username: 'temporary-member',
|
|
167
|
+
password: 'Temporary-example-123!',
|
|
168
|
+
role: 'user',
|
|
169
|
+
accountType: 'temporary',
|
|
170
|
+
accountExpiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
171
|
+
}
|
|
172
|
+
expect(await (await post('admin', '/admin/users/create', input)).json()).toMatchObject({
|
|
173
|
+
success: false,
|
|
174
|
+
error: { code: 'hai:iam:027' },
|
|
175
|
+
})
|
|
120
176
|
const ticket = await proof('admin', 'iam.users.create')
|
|
121
177
|
const response = await post('admin', '/admin/users/create', input, ticket)
|
|
122
178
|
const created = apiContract.haiResultSchema(AdminUserSchema).parse(await response.json())
|
|
@@ -135,7 +191,12 @@ it('再鉴别按用途单次使用,临时账号状态变化立即阻止旧会
|
|
|
135
191
|
|
|
136
192
|
it('设密邀请可完成,投递未知时保留账号;系统管理员可创建其他职责账号', async () => {
|
|
137
193
|
const createdDuty = await post('admin', '/admin/users/create', { username: 'managed-auditor', password: 'Example-123!', role: 'audit_admin' }, await proof('admin', 'iam.users.create'))
|
|
138
|
-
expect(await createdDuty.json()).toMatchObject({
|
|
194
|
+
expect(await createdDuty.json()).toMatchObject({
|
|
195
|
+
success: true,
|
|
196
|
+
data: {
|
|
197
|
+
roles: ['audit_admin'],
|
|
198
|
+
},
|
|
199
|
+
})
|
|
139
200
|
const invite = await post('admin', '/admin/users/invite', { username: 'invited-member', email: 'member@example.test' }, await proof('admin', 'iam.users.invite'))
|
|
140
201
|
expect(await invite.json()).toMatchObject({ success: true })
|
|
141
202
|
const token = invitations.get('invited-member')
|
|
@@ -145,39 +206,94 @@ it('设密邀请可完成,投递未知时保留账号;系统管理员可创
|
|
|
145
206
|
deliveryUnknown = true
|
|
146
207
|
try {
|
|
147
208
|
const unknown = await post('admin', '/admin/users/invite', { username: 'delivery-unknown-member', email: 'unknown@example.test' }, await proof('admin', 'iam.users.invite'))
|
|
148
|
-
expect(await unknown.json()).toMatchObject({
|
|
209
|
+
expect(await unknown.json()).toMatchObject({
|
|
210
|
+
success: false,
|
|
211
|
+
error: { code: 'hai:iam:028' },
|
|
212
|
+
})
|
|
149
213
|
const found = await iam.user.listUsers({ search: 'delivery-unknown-member' })
|
|
150
|
-
expect(found).toMatchObject({
|
|
214
|
+
expect(found).toMatchObject({
|
|
215
|
+
success: true,
|
|
216
|
+
data: { total: 1 },
|
|
217
|
+
})
|
|
151
218
|
}
|
|
152
219
|
finally { deliveryUnknown = false }
|
|
153
220
|
})
|
|
154
221
|
|
|
155
222
|
it('只有审计职责可手动备份且私有归档可验证', async () => {
|
|
156
223
|
const id = crypto.randomUUID()
|
|
157
|
-
expect(await (await post('admin', '/admin/audit/backup', { id })).json()).toMatchObject({
|
|
224
|
+
expect(await (await post('admin', '/admin/audit/backup', { id })).json()).toMatchObject({
|
|
225
|
+
success: false,
|
|
226
|
+
error: { httpStatus: 403 },
|
|
227
|
+
})
|
|
158
228
|
const result = await post('audit_admin', '/admin/audit/backup', { id }, await proof('audit_admin', 'audit.backup'))
|
|
159
229
|
const body: unknown = await result.json()
|
|
160
|
-
expect(body).toMatchObject({
|
|
230
|
+
expect(body).toMatchObject({
|
|
231
|
+
success: true,
|
|
232
|
+
data: {
|
|
233
|
+
id,
|
|
234
|
+
key: expect.stringContaining('hai-private/audit-backups/'),
|
|
235
|
+
sha256: expect.stringMatching(/^[a-f\d]{64}$/),
|
|
236
|
+
},
|
|
237
|
+
})
|
|
161
238
|
const verified = await audit.backup.verify(`hai-private/audit-backups/${id}.jsonl`)
|
|
162
239
|
expect(verified.success).toBe(true)
|
|
163
240
|
})
|
|
164
241
|
|
|
165
|
-
it.each([
|
|
242
|
+
it.each([
|
|
243
|
+
'admin',
|
|
244
|
+
'audit_admin',
|
|
245
|
+
'config_admin',
|
|
246
|
+
'business_operator',
|
|
247
|
+
'user',
|
|
248
|
+
] as const)('系统管理员统一管理 %s 的资料、生命周期、角色和密码', async (role) => {
|
|
166
249
|
const username = `managed-${role}`
|
|
167
250
|
const created = apiContract.haiResultSchema(AdminUserSchema).parse(await (await post('admin', '/admin/users/create', { username, password: 'Managed-example-123!', role }, await proof('admin', 'iam.users.create'))).json())
|
|
168
|
-
expect(created).toMatchObject({
|
|
251
|
+
expect(created).toMatchObject({
|
|
252
|
+
success: true,
|
|
253
|
+
data: {
|
|
254
|
+
roles: [role],
|
|
255
|
+
accountStatus: 'active',
|
|
256
|
+
},
|
|
257
|
+
})
|
|
169
258
|
if (!created.success)
|
|
170
259
|
throw new Error('Managed account fixture failed')
|
|
171
|
-
const input = {
|
|
172
|
-
|
|
260
|
+
const input = {
|
|
261
|
+
id: created.data.id,
|
|
262
|
+
displayName: 'Managed profile',
|
|
263
|
+
accountStatus: 'dormant',
|
|
264
|
+
role: 'user',
|
|
265
|
+
}
|
|
266
|
+
expect(await (await post('admin', '/admin/users/update', input)).json()).toMatchObject({
|
|
267
|
+
success: true,
|
|
268
|
+
data: {
|
|
269
|
+
displayName: 'Managed profile',
|
|
270
|
+
roles: ['user'],
|
|
271
|
+
accountStatus: 'dormant',
|
|
272
|
+
},
|
|
273
|
+
})
|
|
173
274
|
const reset = { id: created.data.id, newPassword: 'Reset-example-456!' }
|
|
174
|
-
expect(await (await post('admin', '/admin/users/reset-password', reset)).json()).toMatchObject({
|
|
275
|
+
expect(await (await post('admin', '/admin/users/reset-password', reset)).json()).toMatchObject({
|
|
276
|
+
success: false,
|
|
277
|
+
error: { code: 'hai:iam:027' },
|
|
278
|
+
})
|
|
175
279
|
const ticket = await proof('admin', 'iam.users.resetPassword')
|
|
176
|
-
expect(await (await post('admin', '/admin/users/reset-password', reset, ticket)).json()).toMatchObject({
|
|
280
|
+
expect(await (await post('admin', '/admin/users/reset-password', reset, ticket)).json()).toMatchObject({
|
|
281
|
+
success: true,
|
|
282
|
+
data: { reset: true },
|
|
283
|
+
})
|
|
177
284
|
expect(await (await post('admin', '/admin/users/reset-password', reset, ticket)).json()).toMatchObject({ success: false })
|
|
178
|
-
expect(await iam.user.getUser(created.data.id)).toMatchObject({
|
|
179
|
-
|
|
285
|
+
expect(await iam.user.getUser(created.data.id)).toMatchObject({
|
|
286
|
+
success: true,
|
|
287
|
+
data: { accountStatus: 'dormant' },
|
|
288
|
+
})
|
|
289
|
+
expect(await iam.auth.login({ identifier: username, password: reset.newPassword })).toMatchObject({
|
|
290
|
+
success: false,
|
|
291
|
+
error: { code: 'hai:iam:002' },
|
|
292
|
+
})
|
|
180
293
|
expect(await (await post('admin', '/admin/users/update', { id: created.data.id, accountStatus: 'active' })).json()).toMatchObject({ success: true })
|
|
181
294
|
expect((await iam.auth.login({ identifier: username, password: reset.newPassword })).success).toBe(true)
|
|
182
|
-
expect(await (await post('audit_admin', '/admin/users/reset-password', reset)).json()).toMatchObject({
|
|
295
|
+
expect(await (await post('audit_admin', '/admin/users/reset-password', reset)).json()).toMatchObject({
|
|
296
|
+
success: false,
|
|
297
|
+
error: { httpStatus: 403 },
|
|
298
|
+
})
|
|
183
299
|
})
|
|
@@ -6,13 +6,17 @@ import { resolveBuildMetadata } from '../../scripts/build-metadata.js'
|
|
|
6
6
|
const buildMetadata = resolveBuildMetadata(fileURLToPath(new URL('.', import.meta.url)))
|
|
7
7
|
|
|
8
8
|
export default defineConfig({
|
|
9
|
-
define: {
|
|
9
|
+
define: {
|
|
10
|
+
__HAI_BUILD_METADATA__: JSON.stringify(buildMetadata),
|
|
11
|
+
},
|
|
10
12
|
async onSuccess() {
|
|
11
13
|
await writeFile(new URL('./dist/build-metadata.json', import.meta.url), JSON.stringify(buildMetadata))
|
|
12
14
|
},
|
|
13
15
|
entry: ['src/index.ts'],
|
|
14
16
|
format: ['esm'],
|
|
15
|
-
dts: {
|
|
17
|
+
dts: {
|
|
18
|
+
compilerOptions: { ignoreDeprecations: '6.0' },
|
|
19
|
+
},
|
|
16
20
|
sourcemap: true,
|
|
17
21
|
clean: true,
|
|
18
22
|
})
|
|
@@ -3,7 +3,9 @@ import { defineConfig } from 'vitest/config'
|
|
|
3
3
|
import { resolveBuildMetadata } from '../../scripts/build-metadata.js'
|
|
4
4
|
|
|
5
5
|
export default defineConfig({
|
|
6
|
-
define: {
|
|
6
|
+
define: {
|
|
7
|
+
__HAI_BUILD_METADATA__: JSON.stringify(resolveBuildMetadata(fileURLToPath(new URL('.', import.meta.url)))),
|
|
8
|
+
},
|
|
7
9
|
test: {
|
|
8
10
|
globals: true,
|
|
9
11
|
environment: 'node',
|
package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs
CHANGED
|
@@ -40,7 +40,12 @@ export async function initializeManagementApi(options: ManagementApiOptions = {}
|
|
|
40
40
|
clientName: options.clientName ?? '{{fullstack.sharedPackageName}}',
|
|
41
41
|
fetch: createCredentialedFetch(options.fetchFn),
|
|
42
42
|
headers: options.headers,
|
|
43
|
-
auth: {
|
|
43
|
+
auth: {
|
|
44
|
+
recoverSession: sessionRecovery.recover,
|
|
45
|
+
terminalType: options.terminalType ?? 'web',
|
|
46
|
+
storage: options.tokenStorage,
|
|
47
|
+
onRefreshFailed: options.onSessionExpired,
|
|
48
|
+
},
|
|
44
49
|
transport: { crypto },
|
|
45
50
|
})
|
|
46
51
|
}
|
|
@@ -65,7 +70,9 @@ export function resolveAvatarUrl(value: string | undefined): string | undefined
|
|
|
65
70
|
function resolveDefaultApiOrigin(): string {
|
|
66
71
|
return resolveRuntimeApiOrigin()
|
|
67
72
|
|| import.meta.env.PUBLIC_API_BASE
|
|
68
|
-
|| (typeof globalThis.location === 'undefined'
|
|
73
|
+
|| (typeof globalThis.location === 'undefined'
|
|
74
|
+
? DEFAULT_API_ORIGIN
|
|
75
|
+
: globalThis.location.origin)
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
/** 跨端浏览器请求携带服务端管理的 httpOnly refresh Cookie。 */
|
package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/feedback-client.ts.hbs
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
{{#if features.feedback}}
|
|
3
3
|
import { createFeedbackClient, createFeedbackReview, saveFeedbackFile } from '@h-ai/feedback/client'
|
|
4
4
|
import { managementApi } from './api-client.js'
|
|
5
|
-
|
|
6
5
|
/** 仅装配共享采集器,不复制采集、脱敏、上传或离线打包逻辑。 */
|
|
7
6
|
export const browserFeedback = createFeedbackClient()
|
|
8
7
|
export const feedbackReview = createFeedbackReview(managementApi.feedback.reports)
|
|
9
8
|
export { feedbackDataBytes } from '@h-ai/feedback/client'
|
|
10
|
-
|
|
11
9
|
export async function initializeBrowserFeedback(metadata: { appVersion: string, buildId: string, commit: string }): Promise<void> {
|
|
12
10
|
const result = await browserFeedback.init({
|
|
13
11
|
appId: '{{projectName}}',
|
|
@@ -26,7 +24,6 @@ export async function initializeBrowserFeedback(metadata: { appVersion: string,
|
|
|
26
24
|
if (!result.success)
|
|
27
25
|
throw new Error(result.error.message, { cause: result.error })
|
|
28
26
|
}
|
|
29
|
-
|
|
30
27
|
/** 下载的内容来自受权 API 或本地采集器;对象 URL 在发起后释放。 */
|
|
31
28
|
export async function downloadFeedback(bytes: Uint8Array, name: string, contentType = 'application/zip'): Promise<void> {
|
|
32
29
|
const result = await saveFeedbackFile(bytes, name, contentType)
|
|
@@ -10,7 +10,19 @@ export const sensitiveActions = createSensitiveActions(authActions)
|
|
|
10
10
|
/** 用途凭据只传给本次 typed client 调用,不使用共享 header。 */
|
|
11
11
|
export async function requestSensitive<T>(operation: string, run: (options: { context: { reauthentication?: string, reauthorize: () => Promise<string | null | undefined> } }) => Promise<HaiResult<T>>): Promise<HaiResult<T>> {
|
|
12
12
|
const ticket = await sensitiveActions.authorize(operation)
|
|
13
|
-
if (ticket === null)
|
|
14
|
-
return err({
|
|
15
|
-
|
|
13
|
+
if (ticket === null) {
|
|
14
|
+
return err({
|
|
15
|
+
code: 'app:cancelled',
|
|
16
|
+
httpStatus: 400,
|
|
17
|
+
message: m.security_action_cancelled(),
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
return run({
|
|
21
|
+
context: {
|
|
22
|
+
...(ticket
|
|
23
|
+
? { reauthentication: ticket }
|
|
24
|
+
: {}),
|
|
25
|
+
reauthorize: () => sensitiveActions.authorize(operation),
|
|
26
|
+
},
|
|
27
|
+
})
|
|
16
28
|
}
|
|
@@ -30,14 +30,38 @@
|
|
|
30
30
|
})
|
|
31
31
|
let menuOpen = $state(false)
|
|
32
32
|
const items = $derived([
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
{
|
|
34
|
+
id: 'users',
|
|
35
|
+
label: m.nav_users(),
|
|
36
|
+
icon: 'icon-[tabler--users]',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'roles',
|
|
40
|
+
label: m.nav_roles(),
|
|
41
|
+
icon: 'icon-[tabler--shield-lock]',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'configuration',
|
|
45
|
+
label: m.business_settings_title(),
|
|
46
|
+
icon: 'icon-[tabler--adjustments]',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'audit',
|
|
50
|
+
label: m.nav_audit(),
|
|
51
|
+
icon: 'icon-[tabler--file-text]',
|
|
52
|
+
},
|
|
37
53
|
{{#if features.feedback}}
|
|
38
|
-
{
|
|
54
|
+
{
|
|
55
|
+
id: 'feedback',
|
|
56
|
+
label: m.nav_feedback(),
|
|
57
|
+
icon: 'icon-[tabler--message-report]',
|
|
58
|
+
},
|
|
39
59
|
{{/if}}
|
|
40
|
-
{
|
|
60
|
+
{
|
|
61
|
+
id: 'settings',
|
|
62
|
+
label: m.settings_title(),
|
|
63
|
+
icon: 'icon-[tabler--settings]',
|
|
64
|
+
},
|
|
41
65
|
].filter(item => store.canOpenPage(item.id)))
|
|
42
66
|
const title = $derived(items.find(item => item.id === store.page)?.label ?? store.info?.name ?? '')
|
|
43
67
|
|
|
@@ -61,7 +85,9 @@
|
|
|
61
85
|
</div>
|
|
62
86
|
<nav class="flex-1 space-y-1 p-3" aria-label={m.navigation()}>
|
|
63
87
|
{#each items as item (item.id)}
|
|
64
|
-
<BareButton class="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left text-sm transition-colors {store.page === item.id
|
|
88
|
+
<BareButton class="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left text-sm transition-colors {store.page === item.id
|
|
89
|
+
? 'bg-primary/10 font-medium text-primary'
|
|
90
|
+
: 'text-base-content/60 hover:bg-base-content/4'}" onclick={() => navigate(item.id)} data-testid="nav-{item.id}">
|
|
65
91
|
<span class="{item.icon} size-4.5"></span>{item.label}
|
|
66
92
|
</BareButton>
|
|
67
93
|
{/each}
|
|
@@ -41,7 +41,13 @@
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
function input(data: FeedbackFormData) {
|
|
44
|
-
return {
|
|
44
|
+
return {
|
|
45
|
+
description: data.description,
|
|
46
|
+
category: data.type,
|
|
47
|
+
contact: data.contact,
|
|
48
|
+
files: data.files,
|
|
49
|
+
removedAttachmentIds: data.removedAttachmentIds,
|
|
50
|
+
}
|
|
45
51
|
}
|
|
46
52
|
async function submit(data: FeedbackFormData) {
|
|
47
53
|
const result = await browserFeedback.submit(input(data))
|
|
@@ -82,5 +88,13 @@
|
|
|
82
88
|
<Button variant='ghost' size='sm' loading={preparing} onclick={() => void prepare()} data-testid='feedback-open'>{m.nav_feedback()}</Button>
|
|
83
89
|
<FeedbackModal bind:open {attachments} budget={browserFeedback.budget} resetKey={draft?.submissionId} onsubmit={submit} oncapture={capturePage} onannotate={annotate} onexport={exportLocal} onfileschange={addFiles}
|
|
84
90
|
evidence={draft?.evidence ?? []}
|
|
85
|
-
evidenceSummary={draft?.missing.length
|
|
91
|
+
evidenceSummary={draft?.missing.length
|
|
92
|
+
? [
|
|
93
|
+
{
|
|
94
|
+
label: m.feedback_logs(),
|
|
95
|
+
status: 'partial',
|
|
96
|
+
detail: draft.missing.join(', '),
|
|
97
|
+
},
|
|
98
|
+
]
|
|
99
|
+
: []} />
|
|
86
100
|
{{/if}}
|
package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs
CHANGED
|
@@ -9,14 +9,44 @@
|
|
|
9
9
|
const { store }: Props = $props()
|
|
10
10
|
let selected = $state<AdminAuditLog | null>(null)
|
|
11
11
|
const columns = $derived([
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
{
|
|
13
|
+
key: 'createdAt',
|
|
14
|
+
sortable: true,
|
|
15
|
+
label: m.created_at(),
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
key: 'username',
|
|
19
|
+
label: m.audit_actor(),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: 'action',
|
|
23
|
+
sortable: true,
|
|
24
|
+
label: m.audit_action(),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: 'category',
|
|
28
|
+
sortable: true,
|
|
29
|
+
label: m.audit_category(),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
key: 'outcome',
|
|
33
|
+
sortable: true,
|
|
34
|
+
label: m.audit_outcome(),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
key: 'severity',
|
|
38
|
+
sortable: true,
|
|
39
|
+
label: m.audit_severity(),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: 'resource',
|
|
43
|
+
sortable: true,
|
|
44
|
+
label: m.audit_resource(),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: 'ipAddress',
|
|
48
|
+
label: m.audit_ip(),
|
|
49
|
+
},
|
|
20
50
|
])
|
|
21
51
|
</script>
|
|
22
52
|
|
|
@@ -30,7 +60,15 @@
|
|
|
30
60
|
event.preventDefault()
|
|
31
61
|
void store.queryAudit()
|
|
32
62
|
}}>
|
|
33
|
-
<FilterBar data-testid='audit-filters' activeCount={[
|
|
63
|
+
<FilterBar data-testid='audit-filters' activeCount={[
|
|
64
|
+
store.auditAction,
|
|
65
|
+
store.auditResource,
|
|
66
|
+
store.auditStartDate,
|
|
67
|
+
store.auditEndDate,
|
|
68
|
+
store.auditCategory,
|
|
69
|
+
store.auditOutcome,
|
|
70
|
+
store.auditSeverity,
|
|
71
|
+
].filter(Boolean).length} onreset={() => {
|
|
34
72
|
store.auditAction = ''
|
|
35
73
|
store.auditResource = ''
|
|
36
74
|
store.auditStartDate = ''
|
|
@@ -48,9 +86,60 @@
|
|
|
48
86
|
<FormField label={m.audit_resource()}><Input size='sm' id="audit-resource" bind:value={store.auditResource} placeholder={m.audit_resource_placeholder()} data-testid="audit-resource" /></FormField>
|
|
49
87
|
<FormField label={m.audit_start()} for="audit-start"><input id="audit-start" type="date" class="input block h-9 min-w-0 w-full" bind:value={store.auditStartDate} /></FormField>
|
|
50
88
|
<FormField label={m.audit_end()} for="audit-end"><input id="audit-end" type="date" class="input block h-9 min-w-0 w-full" bind:value={store.auditEndDate} /></FormField>
|
|
51
|
-
<FormField label={m.audit_category()}><Select size='sm' bind:value={store.auditCategory} options={[
|
|
52
|
-
|
|
53
|
-
|
|
89
|
+
<FormField label={m.audit_category()}><Select size='sm' bind:value={store.auditCategory} options={[
|
|
90
|
+
{
|
|
91
|
+
value: '',
|
|
92
|
+
label: m.audit_all(),
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
value: 'system',
|
|
96
|
+
label: m.audit_category_system(),
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
value: 'business',
|
|
100
|
+
label: m.audit_category_business(),
|
|
101
|
+
},
|
|
102
|
+
]} data-testid='audit-category' /></FormField>
|
|
103
|
+
<FormField label={m.audit_outcome()}><Select size='sm' bind:value={store.auditOutcome} options={[
|
|
104
|
+
{
|
|
105
|
+
value: '',
|
|
106
|
+
label: m.audit_all(),
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
value: 'success',
|
|
110
|
+
label: m.audit_success(),
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
value: 'failure',
|
|
114
|
+
label: m.audit_failure(),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
value: 'denied',
|
|
118
|
+
label: m.audit_denied(),
|
|
119
|
+
},
|
|
120
|
+
]} data-testid='audit-outcome' /></FormField>
|
|
121
|
+
<FormField label={m.audit_severity()}><Select size='sm' bind:value={store.auditSeverity} options={[
|
|
122
|
+
{
|
|
123
|
+
value: '',
|
|
124
|
+
label: m.audit_all(),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
value: 'info',
|
|
128
|
+
label: m.audit_info(),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
value: 'warning',
|
|
132
|
+
label: m.audit_warning(),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
value: 'error',
|
|
136
|
+
label: m.audit_error(),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
value: 'critical',
|
|
140
|
+
label: m.audit_critical(),
|
|
141
|
+
},
|
|
142
|
+
]} data-testid='audit-severity' /></FormField>
|
|
54
143
|
</FormLayout>
|
|
55
144
|
<div class='w-full space-y-2'>
|
|
56
145
|
<p class='text-xs text-base-content/60'>{m.audit_stats_hint()}</p>
|
|
@@ -81,7 +170,9 @@
|
|
|
81
170
|
<dt>{m.audit_request_id()}</dt><dd class='break-all'>{selected.requestId ?? '—'}</dd>
|
|
82
171
|
<dt>{m.audit_trace_id()}</dt><dd class='break-all'>{selected.traceId ?? '—'}</dd>
|
|
83
172
|
<dt>{m.audit_scope()}</dt><dd class='break-all'>{selected.scopeId ?? '—'}</dd>
|
|
84
|
-
<dt>{m.audit_actor_snapshot()}</dt><dd class='break-all'>{selected.actorSnapshot
|
|
173
|
+
<dt>{m.audit_actor_snapshot()}</dt><dd class='break-all'>{selected.actorSnapshot
|
|
174
|
+
? JSON.stringify(selected.actorSnapshot)
|
|
175
|
+
: '—'}</dd>
|
|
85
176
|
<dt class="text-base-content/50">{m.created_at()}</dt><dd>{store.formatDate(selected.createdAt)}</dd>
|
|
86
177
|
<dt class="text-base-content/50">{m.audit_actor()}</dt><dd class="break-all">{selected.username || m.audit_system()}</dd>
|
|
87
178
|
<dt class="text-base-content/50">{m.audit_action()}</dt><dd>{selected.action}</dd>
|