@peanut-admin/admin 0.1.0-alpha.11

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.
Files changed (73) hide show
  1. package/LICENSE +202 -0
  2. package/admin-core/src/access/access.ts +27 -0
  3. package/admin-core/src/access/permission-policy.ts +37 -0
  4. package/admin-core/src/api/client.ts +200 -0
  5. package/admin-core/src/api/problem.ts +67 -0
  6. package/admin-core/src/api/refresh.ts +122 -0
  7. package/admin-core/src/auth/stores.ts +121 -0
  8. package/admin-core/src/auth/tenant-session.ts +32 -0
  9. package/admin-core/src/generated/api.d.ts +22106 -0
  10. package/admin-core/src/governance/audit.ts +56 -0
  11. package/admin-core/src/governance/catalog.ts +86 -0
  12. package/admin-core/src/governance/index.ts +26 -0
  13. package/admin-core/src/governance/menu.ts +63 -0
  14. package/admin-core/src/governance/roles.ts +144 -0
  15. package/admin-core/src/governance/types.ts +64 -0
  16. package/admin-core/src/index.ts +122 -0
  17. package/admin-core/src/lifecycle/tenant.ts +59 -0
  18. package/admin-core/src/module/contribution.ts +124 -0
  19. package/admin-core/src/module/plugin-contribution-policy.ts +51 -0
  20. package/admin-core/src/module/tenant-modules.ts +20 -0
  21. package/admin-core/src/runtime/config.ts +55 -0
  22. package/admin-core/src/runtime/errors.ts +81 -0
  23. package/admin-core/src/runtime/guard.ts +40 -0
  24. package/admin-core/src/runtime/navigation.ts +105 -0
  25. package/admin-core/src/runtime/overrides.ts +214 -0
  26. package/admin-core/src/targets/store.ts +153 -0
  27. package/admin-shell/src/config.ts +84 -0
  28. package/admin-shell/src/deployment-mode.ts +36 -0
  29. package/admin-shell/src/index.ts +44 -0
  30. package/admin-shell/src/layout.ts +332 -0
  31. package/admin-shell/src/overrides.ts +53 -0
  32. package/admin-shell/src/states.ts +93 -0
  33. package/admin-shell/src/tabs.ts +31 -0
  34. package/admin-shell/src/targets.ts +128 -0
  35. package/admin-shell/src/theme.ts +15 -0
  36. package/client-core/src/index.ts +415 -0
  37. package/client-nuxt/src/index.ts +48 -0
  38. package/client-uniapp/src/index.ts +50 -0
  39. package/file-media/src/FileAssetSelector.vue +117 -0
  40. package/file-media/src/FileMediaPage.vue +158 -0
  41. package/file-media/src/contracts.ts +220 -0
  42. package/file-media/src/index.ts +19 -0
  43. package/file-media/src/runtime.ts +210 -0
  44. package/import-export/src/ImportExportPage.vue +155 -0
  45. package/import-export/src/contracts.ts +96 -0
  46. package/import-export/src/index.ts +3 -0
  47. package/import-export/src/runtime.ts +128 -0
  48. package/integration-security/src/IntegrationSecurityPage.vue +402 -0
  49. package/integration-security/src/contracts.ts +171 -0
  50. package/integration-security/src/index.ts +3 -0
  51. package/integration-security/src/runtime.ts +180 -0
  52. package/notification-sms/src/NotificationInboxPage.vue +266 -0
  53. package/notification-sms/src/contracts.ts +195 -0
  54. package/notification-sms/src/index.ts +4 -0
  55. package/notification-sms/src/runtime.ts +143 -0
  56. package/ops-console/src/OpsConsolePage.vue +337 -0
  57. package/ops-console/src/contracts.ts +169 -0
  58. package/ops-console/src/index.ts +3 -0
  59. package/ops-console/src/runtime.ts +199 -0
  60. package/package.json +139 -0
  61. package/reference-codes/src/ReferenceCodesPage.vue +942 -0
  62. package/reference-codes/src/contracts.ts +484 -0
  63. package/reference-codes/src/index.ts +53 -0
  64. package/reference-codes/src/runtime.ts +855 -0
  65. package/settings/src/SettingsPage.vue +536 -0
  66. package/settings/src/contracts.ts +331 -0
  67. package/settings/src/index.ts +45 -0
  68. package/settings/src/runtime.ts +545 -0
  69. package/task-job/src/TaskJobPage.vue +120 -0
  70. package/task-job/src/contracts.ts +117 -0
  71. package/task-job/src/index.ts +2 -0
  72. package/task-job/src/runtime.ts +105 -0
  73. package/testing/src/index.ts +141 -0
@@ -0,0 +1,53 @@
1
+ import {
2
+ defineAdminOverrideSlot,
3
+ } from '@peanut-admin/admin/core'
4
+ import type {
5
+ AdminOverrideRegistry,
6
+ ApiAudience,
7
+ } from '@peanut-admin/admin/core'
8
+ import type { Component } from 'vue'
9
+
10
+ import { AdminShell, PlatformShell } from './layout'
11
+
12
+ export const WORKSPACE_SHELL_OVERRIDE_KEY = 'peanut.shell.service.workspace-component' as const
13
+
14
+ export type WorkspaceShellResolver = (audience: ApiAudience) => Component
15
+
16
+ const defaultWorkspaceShell: WorkspaceShellResolver = audience => (
17
+ audience === 'tenant' ? AdminShell : PlatformShell
18
+ )
19
+
20
+ const isWorkspaceShellResolver = (value: unknown): value is WorkspaceShellResolver => (
21
+ typeof value === 'function'
22
+ )
23
+
24
+ const isVueComponent = (value: unknown): value is Component => {
25
+ if (typeof value === 'function') return true
26
+ if (typeof value !== 'object' || value === null) return false
27
+ const component = value as Record<string, unknown>
28
+ return typeof component.setup === 'function'
29
+ || typeof component.render === 'function'
30
+ || typeof component.template === 'string'
31
+ || typeof component.__asyncLoader === 'function'
32
+ }
33
+
34
+ export const ADMIN_SHELL_OVERRIDE_SLOTS = [
35
+ defineAdminOverrideSlot({
36
+ key: WORKSPACE_SHELL_OVERRIDE_KEY,
37
+ kind: 'service',
38
+ contractVersion: '1.0.0',
39
+ defaultValue: defaultWorkspaceShell,
40
+ validate: isWorkspaceShellResolver,
41
+ }),
42
+ ] as const
43
+
44
+ export type AdminShellOverrideRegistry = AdminOverrideRegistry<typeof ADMIN_SHELL_OVERRIDE_SLOTS>
45
+
46
+ export const resolveWorkspaceShell = (
47
+ registry: AdminShellOverrideRegistry,
48
+ audience: ApiAudience,
49
+ ): Component => {
50
+ const component = registry.get(WORKSPACE_SHELL_OVERRIDE_KEY)(audience)
51
+ if (!isVueComponent(component)) throw new Error('ADMIN_SHELL_OVERRIDE_RESULT_INVALID')
52
+ return component
53
+ }
@@ -0,0 +1,93 @@
1
+ import { ElButton } from 'element-plus'
2
+ import { defineComponent, h } from 'vue'
3
+ import type { PropType } from 'vue'
4
+
5
+ interface StateDefaults {
6
+ title: string
7
+ message: string
8
+ actionLabel?: string
9
+ }
10
+
11
+ const createStateComponent = (name: string, state: string, defaults: StateDefaults) => defineComponent({
12
+ name,
13
+ props: {
14
+ title: { type: String, default: defaults.title },
15
+ message: { type: String, default: defaults.message },
16
+ requestId: { type: String, default: null },
17
+ retryAfter: { type: String as PropType<string | null>, default: null },
18
+ actionLabel: { type: String, default: defaults.actionLabel ?? null },
19
+ onAction: { type: Function as PropType<() => void>, default: null },
20
+ },
21
+ emits: {
22
+ action: () => true,
23
+ },
24
+ setup(props, { emit, slots }) {
25
+ return () => h('section', {
26
+ class: ['pa-state', `pa-state--${name.replace(/State$/, '').toLowerCase()}`],
27
+ 'data-state': state,
28
+ role: 'status',
29
+ 'aria-live': 'polite',
30
+ }, [
31
+ h('h2', { class: 'pa-state__title' }, props.title),
32
+ h('p', { class: 'pa-state__message' }, props.message),
33
+ props.requestId === null
34
+ ? null
35
+ : h('p', { class: 'pa-state__request-id' }, `Request ID: ${props.requestId}`),
36
+ props.retryAfter === null
37
+ ? null
38
+ : h('p', { class: 'pa-state__retry-after' }, `Retry after: ${props.retryAfter}`),
39
+ slots.default?.(),
40
+ props.actionLabel === null
41
+ ? null
42
+ : h(ElButton, {
43
+ onClick: () => {
44
+ emit('action')
45
+ },
46
+ }, () => props.actionLabel),
47
+ ])
48
+ },
49
+ })
50
+
51
+ export const EmptyState = createStateComponent('EmptyState', 'empty', {
52
+ title: 'No data',
53
+ message: 'There is nothing to display.',
54
+ })
55
+
56
+ export const ForbiddenState = createStateComponent('ForbiddenState', 'forbidden', {
57
+ title: 'Access denied',
58
+ message: 'You do not have permission to view this page.',
59
+ })
60
+
61
+ export const NotFoundState = createStateComponent('NotFoundState', 'not-found', {
62
+ title: 'Not found',
63
+ message: 'The requested resource is unavailable.',
64
+ })
65
+
66
+ export const ModuleUnavailableState = createStateComponent('ModuleUnavailableState', 'module-unavailable', {
67
+ title: 'Module unavailable',
68
+ message: 'This module is currently unavailable.',
69
+ actionLabel: 'Retry',
70
+ })
71
+
72
+ export const ConflictState = createStateComponent('ConflictState', 'conflict', {
73
+ title: 'Content changed',
74
+ message: 'Reload the latest version before continuing.',
75
+ actionLabel: 'Reload',
76
+ })
77
+
78
+ export const RateLimitState = createStateComponent('RateLimitState', 'rate-limit', {
79
+ title: 'Too many requests',
80
+ message: 'Wait before trying again.',
81
+ })
82
+
83
+ export const ServiceUnavailableState = createStateComponent('ServiceUnavailableState', 'service-unavailable', {
84
+ title: 'Service unavailable',
85
+ message: 'The service is temporarily unavailable.',
86
+ actionLabel: 'Retry',
87
+ })
88
+
89
+ export const SessionExpiredState = createStateComponent('SessionExpiredState', 'session-expired', {
90
+ title: 'Session expired',
91
+ message: 'Sign in again to continue.',
92
+ actionLabel: 'Sign in',
93
+ })
@@ -0,0 +1,31 @@
1
+ /** Framework-neutral route data required by an admin workspace tab. */
2
+ export interface ShellTabRoute {
3
+ name: unknown
4
+ fullPath: string
5
+ query?: unknown
6
+ meta?: {
7
+ locale?: string
8
+ ignoreCache?: boolean | undefined
9
+ }
10
+ }
11
+
12
+ export interface ShellTab {
13
+ title: string
14
+ name: string
15
+ fullPath: string
16
+ query?: unknown
17
+ ignoreCache?: boolean | undefined
18
+ }
19
+
20
+ export interface ShellTabState {
21
+ tagList: ShellTab[]
22
+ cacheTabList: Set<string>
23
+ }
24
+
25
+ export const tabFromRoute = (route: ShellTabRoute): ShellTab => ({
26
+ title: route.meta?.locale || '',
27
+ name: String(route.name),
28
+ fullPath: route.fullPath,
29
+ query: route.query,
30
+ ignoreCache: route.meta?.ignoreCache,
31
+ })
@@ -0,0 +1,128 @@
1
+ /* eslint-disable vue/one-component-per-file */
2
+
3
+ import type { TargetCandidate, TypedTarget } from '@peanut-admin/admin/core'
4
+ import { ElOption, ElPagination, ElSelect } from 'element-plus'
5
+ import { computed, defineComponent, h } from 'vue'
6
+ import type { Component, PropType } from 'vue'
7
+
8
+ const SelectComponent = ElSelect as unknown as Component
9
+ const OptionComponent = ElOption as unknown as Component
10
+ const PaginationComponent = ElPagination as unknown as Component
11
+
12
+ const targetKey = (target: TypedTarget): string => JSON.stringify([
13
+ target.target_resource_key,
14
+ target.target_role,
15
+ target.target_id,
16
+ ])
17
+
18
+ export const TargetSelector = defineComponent({
19
+ name: 'TargetSelector',
20
+ props: {
21
+ modelValue: {
22
+ type: Array as PropType<readonly TypedTarget[]>,
23
+ default: () => [],
24
+ },
25
+ candidates: {
26
+ type: Array as PropType<readonly TargetCandidate[]>,
27
+ default: () => [],
28
+ },
29
+ multiple: { type: Boolean, default: false },
30
+ loading: { type: Boolean, default: false },
31
+ disabled: { type: Boolean, default: false },
32
+ placeholder: { type: String, default: 'Select target' },
33
+ page: { type: Number, default: 1 },
34
+ pageSize: { type: Number, default: 20 },
35
+ total: { type: Number, default: 0 },
36
+ },
37
+ emits: {
38
+ 'update:modelValue': (targets: readonly TypedTarget[]) => Array.isArray(targets),
39
+ search: (query: string) => typeof query === 'string',
40
+ 'page-change': (page: number) => Number.isInteger(page) && page >= 1,
41
+ },
42
+ setup(props, { emit }) {
43
+ const candidateMap = computed(() => new Map(
44
+ props.candidates.map(candidate => [targetKey(candidate), candidate]),
45
+ ))
46
+ const selectedKeys = computed(() => props.modelValue.map(targetKey))
47
+ const updateSelection = (value: unknown): void => {
48
+ const values = (Array.isArray(value) ? value : [value]).filter(
49
+ (candidate): candidate is string => typeof candidate === 'string',
50
+ )
51
+ const targets = values.flatMap(key => {
52
+ const candidate = candidateMap.value.get(key)
53
+ return candidate === undefined
54
+ ? []
55
+ : [{
56
+ target_resource_key: candidate.target_resource_key,
57
+ target_role: candidate.target_role,
58
+ target_id: candidate.target_id,
59
+ }]
60
+ })
61
+ emit('update:modelValue', props.multiple ? targets : targets.slice(0, 1))
62
+ }
63
+
64
+ return () => h('div', { class: 'pa-target-selector' }, [
65
+ h(SelectComponent, {
66
+ modelValue: props.multiple ? selectedKeys.value : (selectedKeys.value[0] ?? null),
67
+ 'onUpdate:modelValue': updateSelection,
68
+ multiple: props.multiple,
69
+ filterable: true,
70
+ remote: true,
71
+ remoteMethod: (query: string) => emit('search', query),
72
+ loading: props.loading,
73
+ disabled: props.disabled,
74
+ placeholder: props.placeholder,
75
+ class: 'pa-target-selector__select',
76
+ }, () => props.candidates.map(candidate => h(OptionComponent, {
77
+ key: targetKey(candidate),
78
+ value: targetKey(candidate),
79
+ label: candidate.label,
80
+ }))),
81
+ props.total <= props.pageSize
82
+ ? null
83
+ : h(PaginationComponent, {
84
+ class: 'pa-target-selector__pagination',
85
+ currentPage: props.page,
86
+ pageSize: props.pageSize,
87
+ total: props.total,
88
+ layout: 'prev, pager, next',
89
+ 'onUpdate:currentPage': (page: number) => emit('page-change', page),
90
+ }),
91
+ ])
92
+ },
93
+ })
94
+
95
+ export type TargetScopeMode = 'zero' | 'single' | 'multiple' | 'aggregate'
96
+
97
+ export const TargetScopeSummary = defineComponent({
98
+ name: 'TargetScopeSummary',
99
+ props: {
100
+ mode: {
101
+ type: String as PropType<TargetScopeMode>,
102
+ required: true,
103
+ },
104
+ availableCount: { type: Number, required: true },
105
+ selectedCount: { type: Number, default: 0 },
106
+ digest: { type: String, default: null },
107
+ },
108
+ setup(props) {
109
+ const message = computed(() => {
110
+ switch (props.mode) {
111
+ case 'zero':
112
+ return 'No available targets'
113
+ case 'single':
114
+ return '1 available target'
115
+ case 'aggregate':
116
+ return `Read-only aggregate across ${props.availableCount} targets`
117
+ default:
118
+ return `${props.selectedCount} of ${props.availableCount} targets selected`
119
+ }
120
+ })
121
+
122
+ return () => h('div', {
123
+ class: ['pa-target-scope-summary', `is-${props.mode}`],
124
+ role: 'status',
125
+ 'aria-label': 'Target scope',
126
+ }, message.value)
127
+ },
128
+ })
@@ -0,0 +1,15 @@
1
+ export const SHELL_THEME_TOKENS = {
2
+ headerHeight: '--pa-shell-header-height',
3
+ sidebarWidth: '--pa-shell-sidebar-width',
4
+ sidebarCollapsedWidth: '--pa-shell-sidebar-collapsed-width',
5
+ contentMaxWidth: '--pa-shell-content-max-width',
6
+ surfaceColor: '--pa-shell-surface-color',
7
+ borderColor: '--pa-shell-border-color',
8
+ textColor: '--pa-shell-text-color',
9
+ mutedTextColor: '--pa-shell-muted-text-color',
10
+ focusColor: '--pa-shell-focus-color',
11
+ } as const
12
+
13
+ export type ShellThemeToken = typeof SHELL_THEME_TOKENS[keyof typeof SHELL_THEME_TOKENS]
14
+
15
+ export type ShellSlotName = 'header' | 'sidebar' | 'breadcrumb' | 'tabs' | 'default'