@peanut-admin/admin 0.1.0-alpha.4 → 0.1.0-alpha.6
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/admin-core/src/access/permission-policy.ts +37 -0
- package/admin-core/src/auth/tenant-session.ts +32 -0
- package/admin-core/src/index.ts +17 -0
- package/admin-core/src/module/plugin-contribution-policy.ts +51 -0
- package/admin-core/src/module/tenant-modules.ts +20 -0
- package/admin-shell/src/deployment-mode.ts +36 -0
- package/admin-shell/src/index.ts +4 -0
- package/admin-shell/src/tabs.ts +31 -0
- package/package.json +7 -2
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { hasPermission } from './access'
|
|
2
|
+
import { defineAdminOverrideSlot } from '../runtime/overrides'
|
|
3
|
+
|
|
4
|
+
export type PermissionEvaluator = (
|
|
5
|
+
permissions: ReadonlySet<string>,
|
|
6
|
+
permission: string,
|
|
7
|
+
) => boolean
|
|
8
|
+
|
|
9
|
+
export const PERMISSION_EVALUATOR_OVERRIDE_KEY =
|
|
10
|
+
'authorization.permission.service.evaluator' as const
|
|
11
|
+
|
|
12
|
+
const isPermissionEvaluator = (candidate: unknown): candidate is PermissionEvaluator => (
|
|
13
|
+
typeof candidate === 'function'
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
export const permissionEvaluatorSlot = defineAdminOverrideSlot({
|
|
17
|
+
key: PERMISSION_EVALUATOR_OVERRIDE_KEY,
|
|
18
|
+
kind: 'service' as const,
|
|
19
|
+
contractVersion: '1.0.0',
|
|
20
|
+
defaultValue: hasPermission as PermissionEvaluator,
|
|
21
|
+
validate: isPermissionEvaluator,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export const evaluateRequiredPermissions = (
|
|
25
|
+
requiredPermissions: string | string[],
|
|
26
|
+
grantedPermissions: readonly string[],
|
|
27
|
+
evaluator: PermissionEvaluator,
|
|
28
|
+
): boolean => {
|
|
29
|
+
const required = (Array.isArray(requiredPermissions) ? requiredPermissions : [requiredPermissions])
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
if (required.length === 0) return true
|
|
32
|
+
|
|
33
|
+
const permissionSet = new Set(grantedPermissions)
|
|
34
|
+
return permissionSet.has('*') || required.some(
|
|
35
|
+
permission => permission !== '*' && evaluator(permissionSet, permission),
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface TenantChoice {
|
|
2
|
+
tenant_id: number
|
|
3
|
+
tenant_code: string
|
|
4
|
+
tenant_name: string
|
|
5
|
+
member_id: number
|
|
6
|
+
member_display_name: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface TenantSelection {
|
|
10
|
+
state: 'tenant_selection_required'
|
|
11
|
+
challenge_token: string
|
|
12
|
+
expires_at: string
|
|
13
|
+
tenants: TenantChoice[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TenantAuthentication {
|
|
17
|
+
state: 'authenticated'
|
|
18
|
+
access_token: string
|
|
19
|
+
token_type: 'Bearer'
|
|
20
|
+
expires_in: number
|
|
21
|
+
context: {
|
|
22
|
+
tenant_id: string
|
|
23
|
+
account_id: string
|
|
24
|
+
tenant_member_id: string
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type TenantSessionOutcome = TenantSelection | TenantAuthentication
|
|
29
|
+
|
|
30
|
+
export const isMultiTenantDeployment = (value: unknown): boolean => value === 'multi-tenant'
|
|
31
|
+
|
|
32
|
+
export const isTenantAccessToken = (token: string | null): boolean => token?.startsWith('pa_tat_') === true
|
package/admin-core/src/index.ts
CHANGED
|
@@ -14,6 +14,12 @@ export { isProblemCode, parseProblemDetails } from './api/problem'
|
|
|
14
14
|
export type { ProblemDetails, ProblemFieldError } from './api/problem'
|
|
15
15
|
export { hasAllPermissions, hasPermission, useAccess } from './access/access'
|
|
16
16
|
export type { AccessHints } from './access/access'
|
|
17
|
+
export {
|
|
18
|
+
evaluateRequiredPermissions,
|
|
19
|
+
permissionEvaluatorSlot,
|
|
20
|
+
PERMISSION_EVALUATOR_OVERRIDE_KEY,
|
|
21
|
+
} from './access/permission-policy'
|
|
22
|
+
export type { PermissionEvaluator } from './access/permission-policy'
|
|
17
23
|
export {
|
|
18
24
|
usePlatformAuth,
|
|
19
25
|
usePlatformContext,
|
|
@@ -21,10 +27,19 @@ export {
|
|
|
21
27
|
useTenantContext,
|
|
22
28
|
} from './auth/stores'
|
|
23
29
|
export type { PlatformContextData, TenantContextData } from './auth/stores'
|
|
30
|
+
export { isMultiTenantDeployment, isTenantAccessToken } from './auth/tenant-session'
|
|
31
|
+
export type {
|
|
32
|
+
TenantAuthentication,
|
|
33
|
+
TenantChoice,
|
|
34
|
+
TenantSelection,
|
|
35
|
+
TenantSessionOutcome,
|
|
36
|
+
} from './auth/tenant-session'
|
|
24
37
|
export { disposeTenantState, registerTenantDisposer } from './lifecycle/tenant'
|
|
25
38
|
export { createTenantLifecycle } from './lifecycle/tenant'
|
|
26
39
|
export type { TenantDisposer, TenantLifecycle, TenantLifecycleTicket } from './lifecycle/tenant'
|
|
27
40
|
export { createMenuRouteRegistry, defineAdminModule } from './module/contribution'
|
|
41
|
+
export { collectPluginContributions, routesForTenantModules } from './module/plugin-contribution-policy'
|
|
42
|
+
export { enabledTenantModulesFromRoutes } from './module/tenant-modules'
|
|
28
43
|
export type {
|
|
29
44
|
AdminModuleContribution,
|
|
30
45
|
AdminModuleLocaleContribution,
|
|
@@ -35,6 +50,8 @@ export type {
|
|
|
35
50
|
AdminRouteAccess,
|
|
36
51
|
MenuRouteRegistry,
|
|
37
52
|
} from './module/contribution'
|
|
53
|
+
export type { PluginFrontendContribution, PluginFrontendRoute } from './module/plugin-contribution-policy'
|
|
54
|
+
export type { TenantModuleRoute } from './module/tenant-modules'
|
|
38
55
|
export { defineAdminHostConfig } from './runtime/config'
|
|
39
56
|
export type { AdminAudienceHostConfig, AdminHostConfig } from './runtime/config'
|
|
40
57
|
export { mapAdminRuntimeError } from './runtime/errors'
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { PermissionEvaluator } from '../access/permission-policy'
|
|
2
|
+
import { evaluateRequiredPermissions } from '../access/permission-policy'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Minimal router contract shared by host applications without coupling to a
|
|
6
|
+
* particular Vue Router major version.
|
|
7
|
+
*/
|
|
8
|
+
export interface PluginFrontendRoute {
|
|
9
|
+
path: string
|
|
10
|
+
name?: unknown
|
|
11
|
+
component?: unknown
|
|
12
|
+
meta?: {
|
|
13
|
+
requiredPermissions?: string | string[]
|
|
14
|
+
[key: string]: unknown
|
|
15
|
+
}
|
|
16
|
+
children?: PluginFrontendRoute[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PluginFrontendContribution<T extends PluginFrontendRoute = PluginFrontendRoute> {
|
|
20
|
+
moduleKey: string
|
|
21
|
+
routes: T[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const collectPluginContributions = (
|
|
25
|
+
modules: Record<string, { default?: PluginFrontendContribution }>,
|
|
26
|
+
): PluginFrontendContribution[] => Object.keys(modules)
|
|
27
|
+
.sort()
|
|
28
|
+
.map(path => modules[path]?.default)
|
|
29
|
+
.filter((contribution): contribution is PluginFrontendContribution => (
|
|
30
|
+
typeof contribution?.moduleKey === 'string'
|
|
31
|
+
&& contribution.moduleKey.length > 0
|
|
32
|
+
&& Array.isArray(contribution.routes)
|
|
33
|
+
))
|
|
34
|
+
|
|
35
|
+
/** Deployment presence alone never exposes a Tenant Module route. */
|
|
36
|
+
export const routesForTenantModules = <T extends PluginFrontendRoute>(
|
|
37
|
+
contributions: PluginFrontendContribution<T>[],
|
|
38
|
+
enabledModules: readonly string[],
|
|
39
|
+
grantedPermissions: readonly string[],
|
|
40
|
+
evaluator: PermissionEvaluator,
|
|
41
|
+
): T[] => {
|
|
42
|
+
const enabled = new Set(enabledModules)
|
|
43
|
+
return contributions.flatMap((contribution) => {
|
|
44
|
+
if (!enabled.has(contribution.moduleKey)) return []
|
|
45
|
+
return contribution.routes.filter((route) => {
|
|
46
|
+
const required = route.meta?.requiredPermissions
|
|
47
|
+
return (typeof required === 'string' || Array.isArray(required))
|
|
48
|
+
&& evaluateRequiredPermissions(required, grantedPermissions, evaluator)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Minimal route shape needed to derive Tenant module availability. */
|
|
2
|
+
export interface TenantModuleRoute {
|
|
3
|
+
meta?: {
|
|
4
|
+
tenantModuleKey?: unknown
|
|
5
|
+
}
|
|
6
|
+
children?: readonly TenantModuleRoute[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Derive the distinct Tenant module keys exposed by a server-authorized route
|
|
11
|
+
* tree. The host owns fetching and route-to-component resolution.
|
|
12
|
+
*/
|
|
13
|
+
export const enabledTenantModulesFromRoutes = (
|
|
14
|
+
routes: readonly TenantModuleRoute[],
|
|
15
|
+
): string[] => Array.from(new Set(
|
|
16
|
+
routes
|
|
17
|
+
.flatMap(route => [route, ...(route.children ?? [])])
|
|
18
|
+
.map(route => route.meta?.tenantModuleKey)
|
|
19
|
+
.filter((key): key is string => typeof key === 'string'),
|
|
20
|
+
))
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type DeploymentMode = 'standalone' | 'multi-tenant'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal router contract shared by host applications without coupling to a
|
|
5
|
+
* particular Vue Router major version.
|
|
6
|
+
*/
|
|
7
|
+
export interface DeploymentRoute {
|
|
8
|
+
meta?: {
|
|
9
|
+
controlPlane?: unknown
|
|
10
|
+
instanceTool?: boolean
|
|
11
|
+
[key: string]: unknown
|
|
12
|
+
}
|
|
13
|
+
children?: DeploymentRoute[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const deploymentMode = (value: unknown): DeploymentMode => (
|
|
17
|
+
value === 'multi-tenant' ? 'multi-tenant' : 'standalone'
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
export const allowsInstanceTools = (value: unknown): boolean => value === 'standalone'
|
|
21
|
+
|
|
22
|
+
export const routesForDeployment = <T extends DeploymentRoute>(
|
|
23
|
+
routes: T[],
|
|
24
|
+
mode: DeploymentMode,
|
|
25
|
+
instanceToolsAllowed = mode === 'standalone',
|
|
26
|
+
): T[] => routes.reduce<T[]>((visible, route) => {
|
|
27
|
+
if (mode !== 'multi-tenant' && route.meta?.controlPlane !== undefined) return visible
|
|
28
|
+
if (!instanceToolsAllowed && route.meta?.instanceTool === true) return visible
|
|
29
|
+
visible.push({
|
|
30
|
+
...route,
|
|
31
|
+
children: route.children
|
|
32
|
+
? routesForDeployment(route.children, mode, instanceToolsAllowed)
|
|
33
|
+
: route.children,
|
|
34
|
+
} as T)
|
|
35
|
+
return visible
|
|
36
|
+
}, [])
|
package/admin-shell/src/index.ts
CHANGED
|
@@ -29,6 +29,10 @@ export { TargetScopeSummary, TargetSelector } from './targets'
|
|
|
29
29
|
export type { TargetScopeMode } from './targets'
|
|
30
30
|
export { SHELL_THEME_TOKENS } from './theme'
|
|
31
31
|
export type { ShellSlotName, ShellThemeToken } from './theme'
|
|
32
|
+
export { allowsInstanceTools, deploymentMode, routesForDeployment } from './deployment-mode'
|
|
33
|
+
export type { DeploymentMode, DeploymentRoute } from './deployment-mode'
|
|
34
|
+
export { tabFromRoute } from './tabs'
|
|
35
|
+
export type { ShellTab, ShellTabRoute, ShellTabState } from './tabs'
|
|
32
36
|
export {
|
|
33
37
|
ADMIN_SHELL_OVERRIDE_SLOTS,
|
|
34
38
|
resolveWorkspaceShell,
|
|
@@ -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?: any
|
|
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?: any
|
|
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
|
+
})
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peanut-admin/admin",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.6",
|
|
4
4
|
"description": "Reusable Peanut Admin Web services and module contributions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/peanut-opensource/peanut-admin-core"
|
|
10
|
+
},
|
|
7
11
|
"sideEffects": ["*.vue"],
|
|
8
12
|
"files": [
|
|
9
13
|
"admin-core/src",
|
|
@@ -117,7 +121,8 @@
|
|
|
117
121
|
"peerDependencies": {
|
|
118
122
|
"element-plus": "^2.14.3",
|
|
119
123
|
"pinia": ">=2.0.23 <5",
|
|
120
|
-
"vue": "^3.4.21"
|
|
124
|
+
"vue": "^3.4.21",
|
|
125
|
+
"vue-router": ">=4.0.0 <6"
|
|
121
126
|
},
|
|
122
127
|
"peerDependenciesMeta": {
|
|
123
128
|
"element-plus": {
|