@h-ai/cli 0.1.0-alpha.56 → 0.1.0-alpha.58
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 +239 -0
- package/README.ja.md +239 -0
- package/README.md +51 -1
- package/dist/chunk-2H7UOFLK.js +0 -0
- package/dist/chunk-F4IBPVZ5.js +355 -0
- package/dist/chunk-F4IBPVZ5.js.map +1 -0
- package/dist/{chunk-O3ANVICQ.js → chunk-WUSFJFW5.js} +13 -7
- package/dist/chunk-WUSFJFW5.js.map +1 -0
- package/dist/cli-config-templates-6XA7V6CC.js +5 -0
- package/dist/{cli-config-templates-OWBEGBZQ.js.map → cli-config-templates-6XA7V6CC.js.map} +1 -1
- package/dist/{cli-skill-templates-ZIWYMS4Y.js → cli-skill-templates-45CK4RQ2.js} +3 -3
- package/dist/{cli-skill-templates-ZIWYMS4Y.js.map → cli-skill-templates-45CK4RQ2.js.map} +1 -1
- package/dist/index.js +134 -125
- package/dist/index.js.map +1 -1
- package/dist/{src-TBIET7YU.js → src-DUGLX4YB.js} +7270 -2119
- package/dist/src-DUGLX4YB.js.map +1 -0
- package/package.json +17 -17
- package/templates/README.md +4 -4
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +7 -0
- package/templates/apps/admin/src/routes/+page.svelte.hbs +5 -4
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +2 -2
- package/templates/apps/api/.gitignore.hbs +13 -0
- package/templates/apps/api/.npmrc.hbs +1 -0
- package/templates/apps/api/Dockerfile.hbs +6 -5
- package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +1 -1
- package/templates/apps/api/apps/{{projectName}}-contract/src/app-schemas.ts.hbs +2 -0
- package/templates/apps/api/apps/{{projectName}}-contract/tsup.config.ts.hbs +1 -1
- package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +8 -0
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -1
- package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +7 -8
- package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +4 -3
- package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +0 -1
- package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/app-procedures.ts.hbs +4 -0
- package/templates/apps/api/apps/{{projectName}}-service/tests/service.test.ts.hbs +3 -1
- package/templates/apps/api/e2e/api-service.spec.ts.hbs +4 -2
- package/templates/apps/api/{eslint.config.js → eslint.config.js.hbs} +6 -8
- package/templates/apps/api/package.json.hbs +3 -3
- package/templates/apps/api/pnpm-workspace.yaml.hbs +26 -0
- package/templates/apps/api/tsconfig.base.json +0 -1
- package/templates/apps/fullstack/.dockerignore.hbs +3 -0
- package/templates/apps/fullstack/.gitignore.hbs +16 -0
- package/templates/apps/fullstack/.npmrc.hbs +1 -0
- package/templates/apps/fullstack/Dockerfile.hbs +6 -5
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/capacitor.config.ts.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +5 -8
- package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +7 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/messages/en-US.json.hbs +1 -34
- package/templates/apps/fullstack/apps/{{projectName}}-app/messages/zh-CN.json.hbs +1 -34
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +30 -170
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.d.ts.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/lib/capacitor.ts.hbs +6 -5
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/main.ts.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +48 -22
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +3 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/eslint.config.js.hbs +5 -8
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/index.html.hbs +6 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/en-US.json.hbs +1 -28
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/zh-CN.json.hbs +1 -28
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +5 -4
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/App.svelte.hbs +10 -141
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/Cargo.toml.hbs +5 -5
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +3 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +21 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +5 -8
- package/templates/apps/fullstack/apps/{{projectName}}-web/index.html.hbs +6 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/messages/en-US.json.hbs +1 -28
- package/templates/apps/fullstack/apps/{{projectName}}-web/messages/zh-CN.json.hbs +1 -28
- package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/App.svelte.hbs +5 -143
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.d.ts.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/main.ts.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +48 -22
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +207 -17
- package/templates/apps/fullstack/{eslint.config.js → eslint.config.js.hbs} +6 -8
- package/templates/apps/fullstack/package.json.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +2 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +66 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +96 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/app-schemas.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +5 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/tests/contract.test.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/tsup.config.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +13 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_audit.yml.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_cache.yml.hbs +2 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +10 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_db.yml.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_iam.yml.hbs +21 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +10 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_storage.yml.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +10 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +63 -28
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +26 -11
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +276 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +29 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +94 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/app-procedures.ts.hbs +6 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +5 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +98 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +446 -33
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/runtime.test.ts.hbs +48 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tsup.config.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +19 -26
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/en-US.json.hbs +78 -15
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/zh-CN.json.hbs +78 -15
- package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +4 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/index.ts.hbs +3 -4
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +53 -27
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +73 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +55 -45
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AuditManagement.svelte.hbs +61 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ProfileSettings.svelte.hbs +57 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/RoleManagement.svelte.hbs +49 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/UserManagement.svelte.hbs +174 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +399 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/theme-store.svelte.ts.hbs +7 -39
- package/templates/apps/fullstack/packages/{{projectName}}-shared/svelte.config.js.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +151 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +101 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/vitest.config.ts.hbs +7 -0
- package/templates/apps/fullstack/playwright.config.ts.hbs +6 -0
- package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +24 -0
- package/templates/apps/fullstack/tsconfig.base.json +0 -1
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +4 -0
- package/templates/apps/h5/src/routes/+page.svelte.hbs +3 -2
- package/templates/apps/mobile-app/index.html.hbs +2 -2
- package/templates/apps/mobile-app/src/App.svelte.hbs +6 -2
- package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +13 -0
- package/templates/apps/website/src/routes/+layout.svelte.hbs +8 -2
- package/templates/apps/website/src/routes/+page.svelte.hbs +4 -3
- package/templates/base/.npmrc.hbs +1 -0
- package/templates/base/Dockerfile.hbs +9 -8
- package/templates/base/e2e/app.spec.ts.hbs +1 -0
- package/templates/base/eslint-rules.js +45 -0
- package/templates/base/eslint.config.js.hbs +6 -8
- package/templates/base/package.json.hbs +39 -42
- package/templates/base/pnpm-workspace.yaml.hbs +26 -0
- package/templates/base/src/app.d.ts.hbs +4 -0
- package/templates/base/src/hooks.server.ts.hbs +0 -8
- package/templates/base/src/lib/server/init.ts.hbs +26 -5
- package/templates/base/src/routes/+layout.server.ts.hbs +18 -0
- package/templates/base/tests/scaffold.test.ts.hbs +21 -1
- package/templates/base/vite.config.ts.hbs +21 -0
- package/templates/features/ai/routes/api/chat/+server.ts +6 -7
- package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +1 -1
- package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +1 -1
- package/templates/features/iam/routes-shared/api/auth/login/+server.ts +14 -10
- package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +6 -4
- package/templates/features/storage/routes/api/upload/+server.ts +10 -8
- package/templates/skills/AGENTS.md +9 -8
- package/templates/skills/CLAUDE.md +0 -10
- package/templates/skills/bridges/admin/AGENTS.md +8 -0
- package/templates/skills/bridges/admin/copilot-instructions.md +2 -0
- package/templates/skills/bridges/api/AGENTS.md +8 -1
- package/templates/skills/bridges/api/copilot-instructions.md +2 -0
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +21 -4
- package/templates/skills/bridges/fullstack/copilot-instructions.md.hbs +5 -0
- package/templates/skills/bridges/h5/AGENTS.md +8 -0
- package/templates/skills/bridges/h5/copilot-instructions.md +2 -0
- package/templates/skills/bridges/mobile-app/AGENTS.md +8 -0
- package/templates/skills/bridges/mobile-app/copilot-instructions.md +2 -0
- package/templates/skills/bridges/website/AGENTS.md +8 -0
- package/templates/skills/bridges/website/copilot-instructions.md +2 -0
- package/templates/skills/copilot-instructions.md +2 -0
- package/templates/skills/hai-api-client/SKILL.md +2 -0
- package/templates/skills/hai-app-create/SKILL.md +9 -7
- package/templates/skills/hai-app-create/development.md +72 -0
- package/templates/skills/hai-app-create/reference.md +44 -32
- package/templates/skills/hai-app-review/SKILL.md +50 -144
- package/templates/skills/hai-app-tests/SKILL.md +3 -1
- package/templates/skills/hai-app-tests/reference.md +47 -27
- package/templates/skills/hai-build/SKILL.md +5 -4
- package/templates/skills/hai-build/reference.md +56 -34
- package/templates/skills/hai-ci/SKILL.md +8 -15
- package/templates/skills/hai-core/SKILL.md +2 -0
- package/templates/skills/hai-core/reference.md +6 -4
- package/templates/skills/hai-framework-sync/SKILL.md +8 -2
- package/templates/skills/hai-pr-review/SKILL.md +5 -4
- package/templates/skills/hai-reldb/SKILL.md +1 -0
- package/templates/skills/hai-reldb/reference.md +3 -0
- package/templates/skills/hai-scheduler/SKILL.md +3 -0
- package/templates/skills/hai-serv/SKILL.md +2 -0
- package/templates/skills/hai-serv/reference.md +18 -2
- package/templates/skills/hai-storage/SKILL.md +3 -1
- package/templates/skills/hai-ui/SKILL.md +6 -0
- package/templates/skills/hai-ui/reference.md +7 -4
- package/templates/skills/hai-vecdb/reference.md +53 -49
- package/dist/chunk-O3ANVICQ.js.map +0 -1
- package/dist/chunk-WUYLYCW2.js +0 -237
- package/dist/chunk-WUYLYCW2.js.map +0 -1
- package/dist/cli-config-templates-OWBEGBZQ.js +0 -5
- package/dist/src-TBIET7YU.js.map +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/tests/api.test.ts.hbs +0 -28
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/tests/api.test.ts.hbs +0 -28
- package/templates/apps/fullstack/apps/{{projectName}}-web/tests/api.test.ts.hbs +0 -55
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/LanguageSwitcher.svelte.hbs +0 -27
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ThemeSwitcher.svelte.hbs +0 -18
- package/templates/skills/bridges/admin/CLAUDE.md +0 -11
- package/templates/skills/bridges/api/CLAUDE.md +0 -11
- package/templates/skills/bridges/fullstack/CLAUDE.md.hbs +0 -5
- package/templates/skills/bridges/h5/CLAUDE.md +0 -11
- package/templates/skills/bridges/mobile-app/CLAUDE.md +0 -11
- package/templates/skills/bridges/website/CLAUDE.md +0 -11
|
@@ -1,24 +1,87 @@
|
|
|
1
1
|
{{! @skipIfEmpty }}
|
|
2
2
|
{
|
|
3
3
|
"$schema": "https://inlang.com/schema/inlang-message-format",
|
|
4
|
-
"
|
|
5
|
-
"nav_home": "Home",
|
|
6
|
-
"nav_settings": "Settings",
|
|
7
|
-
"settings_title": "Settings",
|
|
4
|
+
"settings_title": "Personal settings",
|
|
8
5
|
"settings_language": "Language",
|
|
9
6
|
"settings_theme": "Theme",
|
|
10
|
-
"shell_footer": "Hai Framework · {platform}",
|
|
11
|
-
"shell_tagline": "Shared shell · Theme · Locale",
|
|
12
|
-
"home_welcome": "Welcome to {name}",
|
|
13
|
-
"home_subtitle": "Hai Framework fullstack starter for web / app / desktop / mini collaboration",
|
|
14
|
-
"home_status_title": "Service status",
|
|
15
7
|
"home_refresh": "Refresh",
|
|
16
8
|
"home_loading": "Loading…",
|
|
17
9
|
"home_error": "Unable to reach the API service",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
10
|
+
"nav_users": "Users",
|
|
11
|
+
"nav_roles": "Roles",
|
|
12
|
+
"nav_audit": "Audit logs",
|
|
13
|
+
"navigation": "Main navigation",
|
|
14
|
+
"open_menu": "Open navigation",
|
|
15
|
+
"close_menu": "Close navigation",
|
|
16
|
+
"logout": "Sign out",
|
|
17
|
+
"login_title": "Sign in",
|
|
18
|
+
"login_description": "Sign in to manage your account and workspace",
|
|
19
|
+
"login_invalid": "The account or password is incorrect. Please try again.",
|
|
20
|
+
"session_expired": "Your session has expired. Please sign in again.",
|
|
21
|
+
"saved": "Changes saved",
|
|
22
|
+
"save": "Save",
|
|
23
|
+
"edit": "Edit",
|
|
24
|
+
"delete": "Delete",
|
|
25
|
+
"cancel": "Cancel",
|
|
26
|
+
"search": "Search",
|
|
27
|
+
"details": "Details",
|
|
28
|
+
"created_at": "Created at",
|
|
29
|
+
"enabled": "Active",
|
|
30
|
+
"disabled": "Disabled",
|
|
31
|
+
"all_statuses": "All statuses",
|
|
32
|
+
"operation_failed": "The operation could not be completed. Please try again.",
|
|
33
|
+
"network_error": "Connection failed. Check your network and try again.",
|
|
34
|
+
"forbidden": "You do not have permission to perform this operation.",
|
|
35
|
+
"conflict": "This account already exists or the change conflicts with the current data.",
|
|
36
|
+
"validation_failed": "Check the information entered and try again.",
|
|
37
|
+
"settings_description": "Manage your profile, preferences and account security",
|
|
38
|
+
"profile_title": "Profile",
|
|
39
|
+
"preferences_title": "Preferences",
|
|
40
|
+
"preferences_description": "Your language and appearance preferences follow your account across devices.",
|
|
41
|
+
"password_title": "Change password",
|
|
42
|
+
"password_rules": "Use at least 8 characters. Choose a different password. You will need to sign in again after changing it.",
|
|
43
|
+
"password_changed": "Password updated. Please sign in with your new password.",
|
|
44
|
+
"avatar_hint": "Upload a PNG, JPEG or WebP image, up to 512 KB.",
|
|
45
|
+
"avatar_invalid": "Choose a PNG, JPEG or WebP image no larger than 512 KB.",
|
|
46
|
+
"avatar_saved": "Avatar updated",
|
|
47
|
+
"avatar_upload": "Upload avatar",
|
|
48
|
+
"users_description": "Create accounts, assign roles and manage access",
|
|
49
|
+
"users_search": "Search username, name or email",
|
|
50
|
+
"user_account": "Account",
|
|
51
|
+
"user_username": "Username",
|
|
52
|
+
"user_display_name": "Display name",
|
|
53
|
+
"user_email": "Email",
|
|
54
|
+
"user_password": "Initial password",
|
|
55
|
+
"user_role": "Role",
|
|
56
|
+
"user_status": "Status",
|
|
57
|
+
"user_create": "Add user",
|
|
58
|
+
"initial_password_rules": "Use at least 8 characters.",
|
|
59
|
+
"account_enabled": "Allow sign-in",
|
|
60
|
+
"users_empty": "No matching accounts. Try another search or create a user.",
|
|
61
|
+
"user_edit": "Edit user",
|
|
62
|
+
"user_deleted": "User deleted",
|
|
63
|
+
"user_delete_title": "Delete user",
|
|
64
|
+
"user_delete_confirm": "Delete {name}? This permanently removes the account and its access.",
|
|
65
|
+
"roles_description": "Two roles keep access simple. Assign roles from user management.",
|
|
66
|
+
"role_admin": "Administrator",
|
|
67
|
+
"role_user": "User",
|
|
68
|
+
"role_admin_capabilities": "Manage users, role descriptions and audit logs, and update personal settings.",
|
|
69
|
+
"role_user_capabilities": "Update personal profile, avatar, language, appearance and password.",
|
|
70
|
+
"role_members": "{count} members",
|
|
71
|
+
"role_edit": "Edit description",
|
|
72
|
+
"role_description": "Description",
|
|
73
|
+
"audit_description": "Review account and administration activity",
|
|
74
|
+
"audit_actor": "Actor",
|
|
75
|
+
"audit_action": "Action",
|
|
76
|
+
"audit_resource": "Resource",
|
|
77
|
+
"audit_resource_id": "Resource ID",
|
|
78
|
+
"audit_ip": "IP address",
|
|
79
|
+
"audit_start": "From",
|
|
80
|
+
"audit_end": "To",
|
|
81
|
+
"audit_action_placeholder": "All actions",
|
|
82
|
+
"audit_resource_placeholder": "All resources",
|
|
83
|
+
"audit_system": "System",
|
|
84
|
+
"audit_details": "Audit log details",
|
|
85
|
+
"audit_date_invalid": "The end date must be on or after the start date.",
|
|
86
|
+
"api_not_configured": "The service connection is not configured. Please contact your administrator."
|
|
24
87
|
}
|
|
@@ -1,24 +1,87 @@
|
|
|
1
1
|
{{! @skipIfEmpty }}
|
|
2
2
|
{
|
|
3
3
|
"$schema": "https://inlang.com/schema/inlang-message-format",
|
|
4
|
-
"
|
|
5
|
-
"nav_home": "首页",
|
|
6
|
-
"nav_settings": "设置",
|
|
7
|
-
"settings_title": "设置",
|
|
4
|
+
"settings_title": "个人设置",
|
|
8
5
|
"settings_language": "语言",
|
|
9
6
|
"settings_theme": "主题",
|
|
10
|
-
"shell_footer": "Hai Framework · {platform}",
|
|
11
|
-
"shell_tagline": "共享 Shell · 主题 · 语言",
|
|
12
|
-
"home_welcome": "欢迎使用 {name}",
|
|
13
|
-
"home_subtitle": "Hai Framework 全栈样板(web / app / desktop / mini 协同)",
|
|
14
|
-
"home_status_title": "服务状态",
|
|
15
7
|
"home_refresh": "刷新",
|
|
16
8
|
"home_loading": "加载中…",
|
|
17
9
|
"home_error": "无法连接到 API 服务",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
10
|
+
"nav_users": "用户管理",
|
|
11
|
+
"nav_roles": "角色管理",
|
|
12
|
+
"nav_audit": "审计日志",
|
|
13
|
+
"navigation": "主导航",
|
|
14
|
+
"open_menu": "打开导航",
|
|
15
|
+
"close_menu": "关闭导航",
|
|
16
|
+
"logout": "退出登录",
|
|
17
|
+
"login_title": "登录",
|
|
18
|
+
"login_description": "登录后管理个人账户与工作空间",
|
|
19
|
+
"login_invalid": "账号或密码不正确,请重试。",
|
|
20
|
+
"session_expired": "登录已过期,请重新登录。",
|
|
21
|
+
"saved": "已保存修改",
|
|
22
|
+
"save": "保存",
|
|
23
|
+
"edit": "编辑",
|
|
24
|
+
"delete": "删除",
|
|
25
|
+
"cancel": "取消",
|
|
26
|
+
"search": "查询",
|
|
27
|
+
"details": "详情",
|
|
28
|
+
"created_at": "创建时间",
|
|
29
|
+
"enabled": "正常",
|
|
30
|
+
"disabled": "已禁用",
|
|
31
|
+
"all_statuses": "全部状态",
|
|
32
|
+
"operation_failed": "操作未完成,请重试。",
|
|
33
|
+
"network_error": "连接失败,请检查网络后重试。",
|
|
34
|
+
"forbidden": "没有执行此操作的权限。",
|
|
35
|
+
"conflict": "账户已存在,或修改与当前数据冲突。",
|
|
36
|
+
"validation_failed": "请检查填写的信息后重试。",
|
|
37
|
+
"settings_description": "管理个人资料、偏好与账户安全",
|
|
38
|
+
"profile_title": "个人资料",
|
|
39
|
+
"preferences_title": "偏好设置",
|
|
40
|
+
"preferences_description": "语言与界面风格保存到个人账户,在不同设备上保持一致。",
|
|
41
|
+
"password_title": "修改密码",
|
|
42
|
+
"password_rules": "密码至少 8 个字符,请使用与旧密码不同的新密码。修改后需要重新登录。",
|
|
43
|
+
"password_changed": "密码已修改,请使用新密码重新登录。",
|
|
44
|
+
"avatar_hint": "支持 PNG、JPEG 和 WebP 图片,大小不超过 512 KB。",
|
|
45
|
+
"avatar_invalid": "请选择不超过 512 KB 的 PNG、JPEG 或 WebP 图片。",
|
|
46
|
+
"avatar_saved": "头像已更新",
|
|
47
|
+
"avatar_upload": "上传头像",
|
|
48
|
+
"users_description": "创建账户、分配角色并管理访问权限",
|
|
49
|
+
"users_search": "搜索用户名、姓名或邮箱",
|
|
50
|
+
"user_account": "账户",
|
|
51
|
+
"user_username": "用户名",
|
|
52
|
+
"user_display_name": "显示名称",
|
|
53
|
+
"user_email": "邮箱",
|
|
54
|
+
"user_password": "初始密码",
|
|
55
|
+
"user_role": "角色",
|
|
56
|
+
"user_status": "状态",
|
|
57
|
+
"user_create": "新增用户",
|
|
58
|
+
"initial_password_rules": "密码至少 8 个字符。",
|
|
59
|
+
"account_enabled": "启用账户",
|
|
60
|
+
"users_empty": "没有匹配的账户,请调整查询条件或新增用户。",
|
|
61
|
+
"user_edit": "编辑用户",
|
|
62
|
+
"user_deleted": "用户已删除",
|
|
63
|
+
"user_delete_title": "删除用户",
|
|
64
|
+
"user_delete_confirm": "确认删除 {name}?此操作将永久删除账户及其访问权限。",
|
|
65
|
+
"roles_description": "以两种角色简化权限管理,在用户管理中分配角色。",
|
|
66
|
+
"role_admin": "管理员",
|
|
67
|
+
"role_user": "普通用户",
|
|
68
|
+
"role_admin_capabilities": "管理用户、角色说明和审计日志,并修改个人设置。",
|
|
69
|
+
"role_user_capabilities": "修改个人资料、头像、语言、界面风格与密码。",
|
|
70
|
+
"role_members": "{count} 名成员",
|
|
71
|
+
"role_edit": "编辑说明",
|
|
72
|
+
"role_description": "角色说明",
|
|
73
|
+
"audit_description": "查看账户与管理操作的审计记录",
|
|
74
|
+
"audit_actor": "操作用户",
|
|
75
|
+
"audit_action": "操作",
|
|
76
|
+
"audit_resource": "资源",
|
|
77
|
+
"audit_resource_id": "资源 ID",
|
|
78
|
+
"audit_ip": "IP 地址",
|
|
79
|
+
"audit_start": "开始日期",
|
|
80
|
+
"audit_end": "结束日期",
|
|
81
|
+
"audit_action_placeholder": "全部操作",
|
|
82
|
+
"audit_resource_placeholder": "全部资源",
|
|
83
|
+
"audit_system": "系统",
|
|
84
|
+
"audit_details": "审计日志详情",
|
|
85
|
+
"audit_date_invalid": "结束日期不能早于开始日期。",
|
|
86
|
+
"api_not_configured": "尚未配置服务连接,请联系应用管理员。"
|
|
24
87
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"engines": { "node": "
|
|
2
|
+
"engines": { "node": "^22.22.2 || ^24.15.0 || >=26.0.0" },
|
|
3
3
|
"name": "{{fullstack.sharedPackageName}}",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"version": "0.1.0",
|
|
@@ -31,10 +31,13 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@h-ai/api-client": "{{haiPackageSpecifier}}",
|
|
34
|
+
"@h-ai/core": "{{haiPackageSpecifier}}",
|
|
35
|
+
"@h-ai/crypto": "{{haiPackageSpecifier}}",
|
|
34
36
|
"@h-ai/ui": "{{haiPackageSpecifier}}",
|
|
35
37
|
"{{fullstack.contractPackageName}}": "workspace:*"
|
|
36
38
|
},
|
|
37
39
|
"devDependencies": {
|
|
40
|
+
"@sveltejs/vite-plugin-svelte": "{{fullstack.packageSpecifiers.sveltePlugin}}",
|
|
38
41
|
"@inlang/paraglide-js": "{{fullstack.packageSpecifiers.inlangParaglide}}",
|
|
39
42
|
"@inlang/plugin-message-format": "{{fullstack.packageSpecifiers.inlangMessageFormat}}",
|
|
40
43
|
"svelte": "{{fullstack.packageSpecifiers.svelte}}",
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
export { default as AppShell } from './lib/components/AppShell.svelte'
|
|
2
|
-
export { default as
|
|
3
|
-
export { default as ThemeSwitcher } from './lib/components/ThemeSwitcher.svelte'
|
|
2
|
+
export { default as AdminWorkspace } from './lib/components/AdminWorkspace.svelte'
|
|
4
3
|
export { AVAILABLE_THEMES, themeStore } from './lib/stores/theme-store.svelte.js'
|
|
5
|
-
export type { AppInfo,
|
|
6
|
-
export {
|
|
4
|
+
export type { AppInfo, ManagementApiOptions } from './lib/api/api-client.js'
|
|
5
|
+
export { initializeManagementApi, managementApi } from './lib/api/api-client.js'
|
|
7
6
|
export * as messages from './lib/paraglide/messages.js'
|
|
8
7
|
export { getLocale, locales, setLocale } from './lib/paraglide/runtime.js'
|
package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs
CHANGED
|
@@ -1,39 +1,61 @@
|
|
|
1
|
+
import type { TokenStorage } from '@h-ai/api-client'
|
|
1
2
|
import type { AppInfoOutputData } from '{{fullstack.contractPackageName}}'
|
|
2
|
-
import { apiClient } from '@h-ai/api-client'
|
|
3
|
+
import { apiClient, HaiApiClientError } from '@h-ai/api-client'
|
|
4
|
+
import { err } from '@h-ai/core'
|
|
5
|
+
import { crypto } from '@h-ai/crypto'
|
|
3
6
|
import { {{fullstack.contractExportName}} } from '{{fullstack.contractPackageName}}'
|
|
4
7
|
|
|
5
8
|
export type AppInfo = AppInfoOutputData
|
|
9
|
+
export const managementApi = apiClient.create({{fullstack.contractExportName}})
|
|
6
10
|
|
|
7
11
|
const DEFAULT_API_ORIGIN = 'http://127.0.0.1:3000'
|
|
8
12
|
|
|
9
|
-
export interface
|
|
13
|
+
export interface ManagementApiOptions {
|
|
10
14
|
fetchFn?: typeof fetch
|
|
11
15
|
baseUrl?: string
|
|
12
16
|
clientName?: string
|
|
17
|
+
tokenStorage?: TokenStorage
|
|
18
|
+
headers?: Record<string, string>
|
|
19
|
+
requireApiBase?: boolean
|
|
20
|
+
onSessionExpired?: () => void
|
|
13
21
|
}
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
/** 所有业务请求由 api-client 统一完成 Token 注入、刷新和双向加密。 */
|
|
24
|
+
export async function initializeManagementApi(options: ManagementApiOptions = {}) {
|
|
25
|
+
if (options.requireApiBase && !options.baseUrl && !resolveRuntimeApiOrigin() && !import.meta.env.PUBLIC_API_BASE)
|
|
26
|
+
return err(HaiApiClientError.CONFIG_ERROR, 'Native applications require a configured API origin')
|
|
27
|
+
if (managementApi.isInitialized)
|
|
28
|
+
await managementApi.close()
|
|
29
|
+
if (!crypto.isInitialized) {
|
|
30
|
+
const result = await crypto.init()
|
|
31
|
+
if (!result.success)
|
|
32
|
+
return result
|
|
33
|
+
}
|
|
19
34
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
return managementApi.init({
|
|
36
|
+
baseUrl: normalizeApiBaseUrl(options.baseUrl ?? resolveDefaultApiOrigin()),
|
|
37
|
+
clientName: options.clientName ?? '{{fullstack.sharedPackageName}}',
|
|
38
|
+
fetch: createCredentialedFetch(options.fetchFn),
|
|
39
|
+
headers: options.headers,
|
|
40
|
+
auth: { storage: options.tokenStorage, onRefreshFailed: options.onSessionExpired },
|
|
41
|
+
transport: { crypto },
|
|
25
42
|
})
|
|
26
|
-
|
|
27
|
-
throw new Error(init.error?.message ?? 'Failed to initialize API client')
|
|
43
|
+
}
|
|
28
44
|
|
|
45
|
+
/** 头像仅允许服务端约定资源路径,并统一指向当前可信 API origin。 */
|
|
46
|
+
export function resolveAvatarUrl(value: string | undefined): string | undefined {
|
|
47
|
+
if (!value)
|
|
48
|
+
return undefined
|
|
29
49
|
try {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
50
|
+
const origin = managementApi.config?.baseUrl ?? normalizeApiBaseUrl(resolveDefaultApiOrigin())
|
|
51
|
+
const parsed = new URL(value, origin)
|
|
52
|
+
const prefix = `${new URL(origin).pathname.replace(/\/+$/, '')}/assets/avatars/`
|
|
53
|
+
if (!parsed.pathname.startsWith(prefix) || !/^[\w-]+\/[\w-]+\.(?:png|jpg|webp)$/.test(parsed.pathname.slice(prefix.length)))
|
|
54
|
+
return undefined
|
|
55
|
+
return new URL(parsed.pathname, origin).toString()
|
|
34
56
|
}
|
|
35
|
-
|
|
36
|
-
|
|
57
|
+
catch {
|
|
58
|
+
return undefined
|
|
37
59
|
}
|
|
38
60
|
}
|
|
39
61
|
|
|
@@ -43,6 +65,14 @@ function resolveDefaultApiOrigin(): string {
|
|
|
43
65
|
|| (typeof globalThis.location === 'undefined' ? DEFAULT_API_ORIGIN : globalThis.location.origin)
|
|
44
66
|
}
|
|
45
67
|
|
|
68
|
+
/** 跨端浏览器请求携带服务端管理的 httpOnly refresh Cookie。 */
|
|
69
|
+
function createCredentialedFetch(fetchFn: typeof fetch = globalThis.fetch.bind(globalThis)): typeof fetch {
|
|
70
|
+
return (input, init) => {
|
|
71
|
+
const request = new Request(input, init)
|
|
72
|
+
return fetchFn(new Request(request, { credentials: 'include' }))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
46
76
|
function resolveRuntimeApiOrigin(): string | undefined {
|
|
47
77
|
const runtimeConfig = (globalThis as typeof globalThis & {
|
|
48
78
|
__HAI_PUBLIC_CONFIG__?: { apiBase?: string }
|
|
@@ -51,12 +81,8 @@ function resolveRuntimeApiOrigin(): string | undefined {
|
|
|
51
81
|
}
|
|
52
82
|
|
|
53
83
|
function normalizeApiBaseUrl(value: string): string {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function trimTrailingSlash(value: string): string {
|
|
61
|
-
return value.replace(/\/+$/, '')
|
|
84
|
+
const url = new URL(value.trim())
|
|
85
|
+
if (url.pathname === '/')
|
|
86
|
+
url.pathname = '/api/v1'
|
|
87
|
+
return url.toString().replace(/\/+$/, '')
|
|
62
88
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ManagementApiOptions } from '../api/api-client.js'
|
|
3
|
+
import { Alert, Skeleton } from '@h-ai/ui/components/compounds/index'
|
|
4
|
+
import { Button } from '@h-ai/ui/components/primitives/index'
|
|
5
|
+
import { LoginForm } from '@h-ai/ui/components/scenes/iam/index'
|
|
6
|
+
import { onMount } from 'svelte'
|
|
7
|
+
import * as m from '../paraglide/messages.js'
|
|
8
|
+
import { ManagementStore } from '../stores/management-store.svelte.js'
|
|
9
|
+
import AppShell from './AppShell.svelte'
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
platform: 'web' | 'app' | 'desktop'
|
|
13
|
+
apiOptions?: ManagementApiOptions
|
|
14
|
+
onLocaleChange?: (locale: string) => void
|
|
15
|
+
}
|
|
16
|
+
const { platform, apiOptions = {}, onLocaleChange }: Props = $props()
|
|
17
|
+
const store = new ManagementStore()
|
|
18
|
+
const loginErrors = $derived({ general: store.error })
|
|
19
|
+
const pageComponent = $derived.by(() => {
|
|
20
|
+
if (store.isAdmin && store.page === 'users')
|
|
21
|
+
return import('./UserManagement.svelte')
|
|
22
|
+
if (store.isAdmin && store.page === 'roles')
|
|
23
|
+
return import('./RoleManagement.svelte')
|
|
24
|
+
if (store.isAdmin && store.page === 'audit')
|
|
25
|
+
return import('./AuditManagement.svelte')
|
|
26
|
+
return import('./ProfileSettings.svelte')
|
|
27
|
+
})
|
|
28
|
+
onMount(() => {
|
|
29
|
+
void store.initialize(apiOptions, onLocaleChange)
|
|
30
|
+
return () => {
|
|
31
|
+
void store.close()
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
$effect(() => {
|
|
35
|
+
if (store.info)
|
|
36
|
+
document.title = store.info.name
|
|
37
|
+
})
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
{#key store.locale}
|
|
41
|
+
{#if store.initializing}
|
|
42
|
+
<div class="grid min-h-dvh place-items-center bg-base-200/50"><div class="w-72 space-y-4"><Skeleton variant="title" /><Skeleton count={3} /><p class="text-center text-sm text-base-content/50">{m.home_loading()}</p></div></div>
|
|
43
|
+
{:else if !store.user}
|
|
44
|
+
<main class="grid min-h-dvh place-items-center bg-base-200/50 px-4 py-10">
|
|
45
|
+
<section class="w-full max-w-md rounded-2xl border border-base-content/8 bg-base-100 p-7 shadow-xl shadow-base-content/3 sm:p-9" data-testid="login-page">
|
|
46
|
+
<div class="mb-7 flex items-center gap-3">
|
|
47
|
+
<span class="grid size-11 place-items-center rounded-xl bg-primary text-primary-content"><span class="icon-[tabler--hexagon-letter-h] size-6"></span></span>
|
|
48
|
+
<div><h1 class="text-lg font-semibold" data-testid="app-name">{store.info?.name || m.login_title()}</h1><p class="mt-1 text-xs text-base-content/45">{m.login_description()}</p></div>
|
|
49
|
+
</div>
|
|
50
|
+
{#if store.notice}<Alert variant="success" class="mb-5" data-testid="notice">{store.notice}</Alert>{/if}
|
|
51
|
+
{#if !store.info}
|
|
52
|
+
<Alert variant="error">{store.error || m.home_error()}</Alert>
|
|
53
|
+
<Button class="mt-5 w-full" variant="primary" onclick={() => void store.initialize(apiOptions, onLocaleChange)}>{m.home_refresh()}</Button>
|
|
54
|
+
{:else}
|
|
55
|
+
<LoginForm loading={store.saving} showRememberMe={false} showForgotPassword={false} errors={loginErrors} onsubmit={data => store.login(data)} data-testid="login-form" />
|
|
56
|
+
<p class="mt-6 text-center text-xs text-base-content/40"><span data-testid="app-version">v{store.info.version}</span> · <span data-testid="app-environment">{store.info.env}</span></p>
|
|
57
|
+
{/if}
|
|
58
|
+
</section>
|
|
59
|
+
</main>
|
|
60
|
+
{:else}
|
|
61
|
+
<AppShell {platform} {store}>
|
|
62
|
+
<div class="space-y-5">
|
|
63
|
+
{#if store.error}<Alert variant="error" data-testid="error">{store.error}</Alert>{/if}
|
|
64
|
+
{#if store.notice}<Alert variant="success" data-testid="notice">{store.notice}</Alert>{/if}
|
|
65
|
+
{#await pageComponent}
|
|
66
|
+
<div class="space-y-4"><Skeleton variant="title" /><Skeleton count={3} /></div>
|
|
67
|
+
{:then { default: Page }}
|
|
68
|
+
<Page {store} />
|
|
69
|
+
{/await}
|
|
70
|
+
</div>
|
|
71
|
+
</AppShell>
|
|
72
|
+
{/if}
|
|
73
|
+
{/key}
|
|
@@ -1,60 +1,70 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte'
|
|
3
|
+
import type { ManagementPage, ManagementStore } from '../stores/management-store.svelte.js'
|
|
4
|
+
import { Avatar, Badge, BareButton, Button } from '@h-ai/ui/components/primitives/index'
|
|
3
5
|
import * as m from '../paraglide/messages.js'
|
|
4
|
-
import LanguageSwitcher from './LanguageSwitcher.svelte'
|
|
5
|
-
import ThemeSwitcher from './ThemeSwitcher.svelte'
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
8
|
-
/** 当前运行的前端平台 */
|
|
9
8
|
platform: 'web' | 'app' | 'desktop' | 'miniapp'
|
|
10
|
-
|
|
11
|
-
onLocaleChange?: (locale: string) => void
|
|
12
|
-
/** 主内容 */
|
|
9
|
+
store: ManagementStore
|
|
13
10
|
children: Snippet
|
|
14
11
|
}
|
|
12
|
+
const { platform, store, children }: Props = $props()
|
|
13
|
+
let menuOpen = $state(false)
|
|
14
|
+
const items = $derived([
|
|
15
|
+
...(store.isAdmin
|
|
16
|
+
? [
|
|
17
|
+
{ id: 'users' as const, label: m.nav_users(), icon: 'icon-[tabler--users]' },
|
|
18
|
+
{ id: 'roles' as const, label: m.nav_roles(), icon: 'icon-[tabler--shield-lock]' },
|
|
19
|
+
{ id: 'audit' as const, label: m.nav_audit(), icon: 'icon-[tabler--file-text]' },
|
|
20
|
+
]
|
|
21
|
+
: []),
|
|
22
|
+
{ id: 'settings' as const, label: m.settings_title(), icon: 'icon-[tabler--settings]' },
|
|
23
|
+
])
|
|
24
|
+
const title = $derived(items.find(item => item.id === store.page)?.label ?? m.settings_title())
|
|
15
25
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (value === 'web')
|
|
20
|
-
return m.platform_web()
|
|
21
|
-
if (value === 'app')
|
|
22
|
-
return m.platform_app()
|
|
23
|
-
if (value === 'desktop')
|
|
24
|
-
return m.platform_desktop()
|
|
25
|
-
return m.platform_miniapp()
|
|
26
|
+
function navigate(page: ManagementPage) {
|
|
27
|
+
menuOpen = false
|
|
28
|
+
void store.navigate(page)
|
|
26
29
|
}
|
|
27
|
-
|
|
28
|
-
const platformLabel = $derived(getPlatformLabel(platform))
|
|
29
30
|
</script>
|
|
30
31
|
|
|
31
|
-
<div class="min-h-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
<div class="flex min-h-dvh bg-base-200/50 text-base-content" class:hai-safe-area={platform === 'app'}>
|
|
33
|
+
{#if menuOpen}
|
|
34
|
+
<BareButton class="fixed inset-0 z-30 bg-black/30 lg:hidden" ariaLabel={m.close_menu()} onclick={() => { menuOpen = false }} />
|
|
35
|
+
{/if}
|
|
36
|
+
<aside class="fixed inset-y-0 left-0 z-40 flex w-60 flex-col border-r border-base-content/8 bg-base-100 transition-transform duration-200 lg:sticky lg:top-0 lg:h-dvh lg:translate-x-0 {menuOpen ? 'translate-x-0' : '-translate-x-full'}">
|
|
37
|
+
<div class="flex h-16 items-center gap-3 border-b border-base-content/8 px-5">
|
|
38
|
+
<span class="grid size-8 shrink-0 place-items-center rounded-lg bg-primary text-primary-content"><span class="icon-[tabler--hexagon-letter-h] size-5"></span></span>
|
|
39
|
+
<span class="truncate text-sm font-semibold" data-testid="app-name">{store.info?.name}</span>
|
|
40
|
+
</div>
|
|
41
|
+
<nav class="flex-1 space-y-1 p-3" aria-label={m.navigation()}>
|
|
42
|
+
{#each items as item (item.id)}
|
|
43
|
+
<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 ? 'bg-primary/10 font-medium text-primary' : 'text-base-content/60 hover:bg-base-content/4'}" onclick={() => navigate(item.id)} data-testid="nav-{item.id}">
|
|
44
|
+
<span class="{item.icon} size-4.5"></span>{item.label}
|
|
45
|
+
</BareButton>
|
|
46
|
+
{/each}
|
|
47
|
+
</nav>
|
|
48
|
+
<div class="border-t border-base-content/8 p-4 text-xs text-base-content/40"><span data-testid="app-version">v{store.info?.version}</span></div>
|
|
49
|
+
</aside>
|
|
50
|
+
<div class="flex min-w-0 flex-1 flex-col">
|
|
51
|
+
<header class="sticky top-0 z-20 flex h-16 items-center justify-between gap-2 border-b border-base-content/8 bg-base-100/95 px-3 backdrop-blur sm:px-4 lg:px-6">
|
|
52
|
+
<div class="flex min-w-0 flex-1 items-center gap-2 sm:gap-3">
|
|
53
|
+
<BareButton class="rounded-lg p-2 hover:bg-base-content/5 lg:hidden" ariaLabel={m.open_menu()} onclick={() => { menuOpen = !menuOpen }} data-testid="mobile-menu"><span class="icon-[tabler--menu-2] size-5"></span></BareButton>
|
|
54
|
+
<span class="truncate text-sm font-medium">{title}</span>
|
|
38
55
|
</div>
|
|
39
|
-
<div>
|
|
40
|
-
<
|
|
41
|
-
|
|
56
|
+
<div class="flex shrink-0 items-center gap-1.5 sm:gap-3">
|
|
57
|
+
<Badge variant="ghost" size="sm" data-testid="app-environment">{store.info?.env}</Badge>
|
|
58
|
+
{#if store.user}
|
|
59
|
+
<BareButton class="flex items-center gap-2 rounded-lg px-1 py-1 hover:bg-base-content/5 sm:px-2" ariaLabel={m.settings_title()} onclick={() => navigate('settings')}>
|
|
60
|
+
<Avatar name={store.user.displayName || store.user.username} src={store.user.avatarUrl} size="sm" />
|
|
61
|
+
<span class="hidden max-w-36 truncate text-sm sm:block">{store.user.displayName || store.user.username}</span>
|
|
62
|
+
</BareButton>
|
|
63
|
+
{/if}
|
|
64
|
+
<Button variant="ghost" size="sm" loading={store.saving} onclick={() => void store.logout()} data-testid="logout">{m.logout()}</Button>
|
|
42
65
|
</div>
|
|
43
|
-
|
|
44
|
-
</
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
<LanguageSwitcher {onLocaleChange} />
|
|
48
|
-
</div>
|
|
49
|
-
</header>
|
|
50
|
-
|
|
51
|
-
<main class="flex-1 px-4 py-8 lg:px-8 lg:py-10">
|
|
52
|
-
<div class="mx-auto w-full max-w-6xl">
|
|
53
|
-
{@render children()}
|
|
54
|
-
</div>
|
|
55
|
-
</main>
|
|
56
|
-
|
|
57
|
-
<footer class="footer footer-center border-t border-base-300/70 bg-base-100/70 p-4 text-sm text-base-content/60 backdrop-blur-xl">
|
|
58
|
-
<span>{m.shell_footer({ platform: platformLabel })}</span>
|
|
59
|
-
</footer>
|
|
66
|
+
</header>
|
|
67
|
+
<main class="mx-auto w-full max-w-7xl flex-1 p-4 pb-8 sm:p-6 lg:p-8">{@render children()}</main>
|
|
68
|
+
<footer class="border-t border-base-content/8 px-6 py-3 text-center text-xs text-base-content/35">{store.info?.name} · v{store.info?.version}</footer>
|
|
69
|
+
</div>
|
|
60
70
|
</div>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { AdminAuditLog } from '{{fullstack.contractPackageName}}'
|
|
3
|
+
import type { ManagementStore } from '../stores/management-store.svelte.js'
|
|
4
|
+
import { DataTable, Modal, Pagination } from '@h-ai/ui/components/compounds/index'
|
|
5
|
+
import { Button, Input } from '@h-ai/ui/components/primitives/index'
|
|
6
|
+
import * as m from '../paraglide/messages.js'
|
|
7
|
+
|
|
8
|
+
interface Props { store: ManagementStore }
|
|
9
|
+
const { store }: Props = $props()
|
|
10
|
+
let selected = $state<AdminAuditLog | null>(null)
|
|
11
|
+
const columns = $derived([
|
|
12
|
+
{ key: 'createdAt', label: m.created_at() },
|
|
13
|
+
{ key: 'username', label: m.audit_actor() },
|
|
14
|
+
{ key: 'action', label: m.audit_action() },
|
|
15
|
+
{ key: 'resource', label: m.audit_resource() },
|
|
16
|
+
{ key: 'ipAddress', label: m.audit_ip() },
|
|
17
|
+
])
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<section class="space-y-5" data-testid="audit-page">
|
|
21
|
+
<div><h1 class="text-xl font-semibold tracking-tight">{m.nav_audit()}</h1><p class="mt-1 text-sm text-base-content/50">{m.audit_description()}</p></div>
|
|
22
|
+
<div class="rounded-xl border border-base-content/8 bg-base-100">
|
|
23
|
+
<form class="grid items-end gap-3 border-b border-base-content/8 p-4 sm:grid-cols-2 xl:grid-cols-[repeat(4,minmax(0,1fr))_auto] [&_.fieldset]:p-0" onsubmit={(event) => {
|
|
24
|
+
event.preventDefault()
|
|
25
|
+
void store.loadAudit()
|
|
26
|
+
}}>
|
|
27
|
+
<div class="min-w-0 space-y-1"><label class="block text-xs text-base-content/60" for="audit-action">{m.audit_action()}</label><Input id="audit-action" bind:value={store.auditAction} placeholder={m.audit_action_placeholder()} data-testid="audit-action" /></div>
|
|
28
|
+
<div class="min-w-0 space-y-1"><label class="block text-xs text-base-content/60" for="audit-resource">{m.audit_resource()}</label><Input id="audit-resource" bind:value={store.auditResource} placeholder={m.audit_resource_placeholder()} data-testid="audit-resource" /></div>
|
|
29
|
+
<div class="min-w-0 space-y-1"><label class="block text-xs text-base-content/60" for="audit-start">{m.audit_start()}</label><input id="audit-start" type="date" class="input block h-10 min-w-0 w-full" bind:value={store.auditStartDate} /></div>
|
|
30
|
+
<div class="min-w-0 space-y-1"><label class="block text-xs text-base-content/60" for="audit-end">{m.audit_end()}</label><input id="audit-end" type="date" class="input block h-10 min-w-0 w-full" bind:value={store.auditEndDate} /></div>
|
|
31
|
+
<Button type="submit" variant="outline" disabled={store.loading} class="h-10 w-full">{m.search()}</Button>
|
|
32
|
+
</form>
|
|
33
|
+
<div class="overflow-x-auto">
|
|
34
|
+
<DataTable data={store.audit} {columns} keyField="id" loading={store.loading} striped={false}>
|
|
35
|
+
{#snippet cell(entry, key)}
|
|
36
|
+
{#if key === 'createdAt'}<span class="whitespace-nowrap text-xs">{store.formatDate(entry.createdAt)}</span>
|
|
37
|
+
{:else if key === 'username'}{entry.username || m.audit_system()}
|
|
38
|
+
{:else if key === 'action'}<span class="rounded-md bg-base-200 px-2 py-1 font-mono text-xs">{entry.action}</span>
|
|
39
|
+
{:else if key === 'resource'}<span class="text-sm">{entry.resource}</span>
|
|
40
|
+
{:else if key === 'ipAddress'}<span class="text-xs text-base-content/55">{entry.ipAddress || '—'}</span>{/if}
|
|
41
|
+
{/snippet}
|
|
42
|
+
{#snippet actions(entry)}<Button size="xs" variant="ghost" onclick={() => { selected = entry }}>{m.details()}</Button>{/snippet}
|
|
43
|
+
</DataTable>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="border-t border-base-content/8 p-4"><Pagination page={store.auditPage} total={store.auditTotal} pageSize={store.pageSize} onchange={page => void store.loadAudit(page)} /></div>
|
|
46
|
+
</div>
|
|
47
|
+
</section>
|
|
48
|
+
|
|
49
|
+
<Modal open={!!selected} title={m.audit_details()} onclose={() => { selected = null }}>
|
|
50
|
+
{#if selected}
|
|
51
|
+
<dl class="grid grid-cols-[auto_1fr] gap-x-5 gap-y-3 text-sm">
|
|
52
|
+
<dt class="text-base-content/50">{m.created_at()}</dt><dd>{store.formatDate(selected.createdAt)}</dd>
|
|
53
|
+
<dt class="text-base-content/50">{m.audit_actor()}</dt><dd class="break-all">{selected.username || m.audit_system()}</dd>
|
|
54
|
+
<dt class="text-base-content/50">{m.audit_action()}</dt><dd>{selected.action}</dd>
|
|
55
|
+
<dt class="text-base-content/50">{m.audit_resource()}</dt><dd>{selected.resource}</dd>
|
|
56
|
+
<dt class="text-base-content/50">{m.audit_resource_id()}</dt><dd class="break-all">{selected.resourceId || '—'}</dd>
|
|
57
|
+
<dt class="text-base-content/50">{m.audit_ip()}</dt><dd>{selected.ipAddress || '—'}</dd>
|
|
58
|
+
</dl>
|
|
59
|
+
<pre class="mt-5 max-h-72 overflow-auto whitespace-pre-wrap rounded-lg bg-base-200 p-4 text-xs">{selected.details || '—'}</pre>
|
|
60
|
+
{/if}
|
|
61
|
+
</Modal>
|