@swiss-ai-hub/web 0.303.0 → 0.304.0
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/components/Role/AccessCapabilities.vue +67 -0
- package/components/Role/AccessCapabilityGroup.vue +130 -0
- package/components/Role/AccessRulesEditor.vue +176 -95
- package/composables/access/useAccessCapabilities.ts +49 -0
- package/composables/access/useAccessPresets.ts +28 -0
- package/i18n/locales/de.yaml +17 -0
- package/i18n/locales/en.yaml +16 -0
- package/i18n/locales/fr.yaml +17 -0
- package/i18n/locales/it.yaml +17 -0
- package/package.json +1 -1
- package/pages/[tenant]/service/roles.vue +1 -0
- package/pages/[tenant]/service/users/[user_id].vue +8 -44
- package/sdk/client/index.ts +15 -0
- package/sdk/client/schemas.gen.ts +197 -3
- package/sdk/client/sdk.gen.ts +72 -0
- package/sdk/client/transformers.gen.ts +24 -0
- package/sdk/client/types.gen.ts +218 -2
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col gap-1 rounded-lg border border-surface-200 p-6 dark:border-surface-700">
|
|
3
|
+
<div class="flex items-center gap-2">
|
|
4
|
+
<span class="text-xl font-bold">
|
|
5
|
+
{{ readonly ? t('role.capabilities_readonly_title') : t('role.capabilities_title') }}
|
|
6
|
+
</span>
|
|
7
|
+
<ProgressSpinner
|
|
8
|
+
v-if="capabilitiesAreLoading"
|
|
9
|
+
style="width: 1rem; height: 1rem"
|
|
10
|
+
stroke-width="6"
|
|
11
|
+
/>
|
|
12
|
+
</div>
|
|
13
|
+
<p class="text-xs text-surface-500 dark:text-surface-400">
|
|
14
|
+
{{ readonly ? t('role.capabilities_readonly_subtitle') : t('role.capabilities_subtitle') }}
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
<div class="mt-5 flex flex-col gap-10">
|
|
18
|
+
<AccessCapabilityGroup
|
|
19
|
+
v-for="group in capabilities?.groups ?? []"
|
|
20
|
+
:key="group.key"
|
|
21
|
+
:group="group"
|
|
22
|
+
:depth="0"
|
|
23
|
+
:readonly="readonly"
|
|
24
|
+
@add="(rule) => emit('add', rule)"
|
|
25
|
+
@remove="(rule) => emit('remove', rule)"
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
<p
|
|
29
|
+
v-if="!capabilitiesAreLoading && !(capabilities?.groups?.length)"
|
|
30
|
+
class="text-sm italic text-surface-500 dark:text-surface-400"
|
|
31
|
+
>
|
|
32
|
+
{{ t('role.capabilities_empty') }}
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
import { useI18n } from 'vue-i18n'
|
|
39
|
+
|
|
40
|
+
import AccessCapabilityGroup from './AccessCapabilityGroup.vue'
|
|
41
|
+
|
|
42
|
+
import { useAccessCapabilities } from '@/composables/access/useAccessCapabilities'
|
|
43
|
+
|
|
44
|
+
const { t } = useI18n()
|
|
45
|
+
|
|
46
|
+
const props = withDefaults(defineProps<{
|
|
47
|
+
rules: string[]
|
|
48
|
+
restrictToTenant?: boolean
|
|
49
|
+
isSysAdmin?: boolean
|
|
50
|
+
readonly?: boolean
|
|
51
|
+
}>(), {
|
|
52
|
+
restrictToTenant: true,
|
|
53
|
+
isSysAdmin: false,
|
|
54
|
+
readonly: false,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const emit = defineEmits<{
|
|
58
|
+
add: [rule: string]
|
|
59
|
+
remove: [rule: string]
|
|
60
|
+
}>()
|
|
61
|
+
|
|
62
|
+
const { capabilities, capabilitiesAreLoading } = useAccessCapabilities(
|
|
63
|
+
() => props.rules,
|
|
64
|
+
() => props.restrictToTenant,
|
|
65
|
+
() => props.isSysAdmin,
|
|
66
|
+
)
|
|
67
|
+
</script>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col">
|
|
3
|
+
<!-- Service header: icon + name + hairline rule -->
|
|
4
|
+
<div
|
|
5
|
+
v-if="depth === 0"
|
|
6
|
+
class="flex items-center gap-2.5 border-b border-surface-200 pb-2.5 dark:border-surface-800"
|
|
7
|
+
>
|
|
8
|
+
<Icon
|
|
9
|
+
v-if="group.icon"
|
|
10
|
+
:name="group.icon"
|
|
11
|
+
size="1.4em"
|
|
12
|
+
class="text-surface-500 dark:text-surface-400"
|
|
13
|
+
/>
|
|
14
|
+
<span class="text-lg font-bold">{{ group.label }}</span>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<!-- Class subtitle -->
|
|
18
|
+
<div
|
|
19
|
+
v-else-if="depth === 1"
|
|
20
|
+
class="flex items-center gap-2"
|
|
21
|
+
>
|
|
22
|
+
<Icon
|
|
23
|
+
v-if="group.icon"
|
|
24
|
+
:name="group.icon"
|
|
25
|
+
size="1.2em"
|
|
26
|
+
class="text-surface-500 dark:text-surface-400"
|
|
27
|
+
/>
|
|
28
|
+
<span class="text-base font-semibold text-surface-700 dark:text-surface-200">
|
|
29
|
+
{{ group.label }}
|
|
30
|
+
</span>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- Instance subtitle -->
|
|
34
|
+
<div
|
|
35
|
+
v-else
|
|
36
|
+
class="flex items-center gap-2 text-sm font-medium text-surface-600 dark:text-surface-300"
|
|
37
|
+
>
|
|
38
|
+
<span class="size-1.5 rounded-full bg-surface-300 dark:bg-surface-600" />
|
|
39
|
+
{{ group.label }}
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Capabilities -->
|
|
43
|
+
<div
|
|
44
|
+
class="flex flex-col gap-1"
|
|
45
|
+
:class="depth === 0 ? 'mt-3' : 'mt-2'"
|
|
46
|
+
>
|
|
47
|
+
<label
|
|
48
|
+
v-for="cap in group.capabilities ?? []"
|
|
49
|
+
:key="cap.key"
|
|
50
|
+
class="group/cap flex items-start gap-3 rounded-md py-1 pr-1"
|
|
51
|
+
:class="readonly || !cap.toggleable || cap.locked ? 'cursor-default' : 'cursor-pointer'"
|
|
52
|
+
>
|
|
53
|
+
<Checkbox
|
|
54
|
+
:model-value="cap.granted"
|
|
55
|
+
binary
|
|
56
|
+
:disabled="readonly || !cap.toggleable || cap.locked"
|
|
57
|
+
size="small"
|
|
58
|
+
class="mt-0.5"
|
|
59
|
+
@update:model-value="(value) => onToggle(cap, value)"
|
|
60
|
+
/>
|
|
61
|
+
<span class="flex min-w-0 flex-1 flex-col">
|
|
62
|
+
<span class="flex items-center gap-1.5 text-sm leading-tight">
|
|
63
|
+
{{ cap.label }}
|
|
64
|
+
<template v-if="!readonly">
|
|
65
|
+
<i
|
|
66
|
+
v-if="cap.locked"
|
|
67
|
+
v-tooltip.top="t('role.capability_locked')"
|
|
68
|
+
class="pi pi-lock text-[10px] text-surface-400"
|
|
69
|
+
/>
|
|
70
|
+
<i
|
|
71
|
+
v-else-if="!cap.toggleable"
|
|
72
|
+
v-tooltip.top="t('role.capability_readonly')"
|
|
73
|
+
class="pi pi-eye text-[10px] text-surface-400"
|
|
74
|
+
/>
|
|
75
|
+
</template>
|
|
76
|
+
</span>
|
|
77
|
+
<span class="mt-0.5 text-xs leading-snug text-surface-500 dark:text-surface-400">{{ cap.description }}</span>
|
|
78
|
+
</span>
|
|
79
|
+
<code
|
|
80
|
+
v-if="cap.rule"
|
|
81
|
+
class="mt-0.5 shrink-0 font-mono text-[11px] text-surface-300 transition-colors group-hover/cap:text-surface-500 dark:text-surface-600 dark:group-hover/cap:text-surface-400"
|
|
82
|
+
>{{ cap.rule }}</code>
|
|
83
|
+
</label>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<!-- Nested groups (classes, then instances) -->
|
|
87
|
+
<div
|
|
88
|
+
v-if="group.groups?.length"
|
|
89
|
+
class="mt-4 flex flex-col gap-6"
|
|
90
|
+
:class="depth === 0 ? 'pl-1' : 'pl-4'"
|
|
91
|
+
>
|
|
92
|
+
<AccessCapabilityGroup
|
|
93
|
+
v-for="sub in group.groups ?? []"
|
|
94
|
+
:key="sub.key"
|
|
95
|
+
:group="sub"
|
|
96
|
+
:depth="depth + 1"
|
|
97
|
+
:readonly="readonly"
|
|
98
|
+
@add="(rule) => emit('add', rule)"
|
|
99
|
+
@remove="(rule) => emit('remove', rule)"
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</template>
|
|
104
|
+
|
|
105
|
+
<script setup lang="ts">
|
|
106
|
+
import { useI18n } from 'vue-i18n'
|
|
107
|
+
|
|
108
|
+
import type { Capability, CapabilityGroup } from '@core/sdk/client'
|
|
109
|
+
|
|
110
|
+
const { t } = useI18n()
|
|
111
|
+
|
|
112
|
+
const props = withDefaults(defineProps<{
|
|
113
|
+
group: CapabilityGroup
|
|
114
|
+
depth?: number
|
|
115
|
+
readonly?: boolean
|
|
116
|
+
}>(), {
|
|
117
|
+
depth: 0,
|
|
118
|
+
readonly: false,
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const emit = defineEmits<{
|
|
122
|
+
add: [rule: string]
|
|
123
|
+
remove: [rule: string]
|
|
124
|
+
}>()
|
|
125
|
+
|
|
126
|
+
const onToggle = (cap: Capability, value: boolean) => {
|
|
127
|
+
if (props.readonly || !cap.rule) return
|
|
128
|
+
emit(value ? 'add' : 'remove', cap.rule)
|
|
129
|
+
}
|
|
130
|
+
</script>
|
|
@@ -1,116 +1,193 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex flex-col gap-
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<
|
|
17
|
-
|
|
2
|
+
<div class="flex flex-col gap-4">
|
|
3
|
+
<div class="flex flex-col gap-2 rounded-lg border border-surface-200 p-4 dark:border-surface-700">
|
|
4
|
+
<span class="flex items-center gap-1 font-semibold">
|
|
5
|
+
{{ t('role.access_rules') }}
|
|
6
|
+
<i
|
|
7
|
+
class="pi pi-question-circle text-surface-400"
|
|
8
|
+
@mouseenter="(e: Event) => accessRulesHelp?.show(e)"
|
|
9
|
+
@mouseleave="() => accessRulesHelp?.hide()"
|
|
10
|
+
/>
|
|
11
|
+
<Popover ref="accessRulesHelp">
|
|
12
|
+
<div class="text-sm font-normal">
|
|
13
|
+
<p class="mb-2">
|
|
14
|
+
{{ t('role.access_rules_help_intro') }}
|
|
15
|
+
</p>
|
|
16
|
+
<ul class="flex flex-col gap-1">
|
|
17
|
+
<li class="whitespace-nowrap"><Badge
|
|
18
|
+
value="aihub.user.agent.>"
|
|
19
|
+
severity="secondary"
|
|
20
|
+
size="small"
|
|
21
|
+
/> — {{ t('role.access_rules_help_all_agents') }}</li>
|
|
22
|
+
<li class="whitespace-nowrap"><Badge
|
|
23
|
+
value="aihub.user.agent.MyAgent.*"
|
|
24
|
+
severity="secondary"
|
|
25
|
+
size="small"
|
|
26
|
+
/> — {{ t('role.access_rules_help_agent_instances') }}</li>
|
|
27
|
+
<li class="whitespace-nowrap"><Badge
|
|
28
|
+
value="aihub.user.service.knowledge"
|
|
29
|
+
severity="secondary"
|
|
30
|
+
size="small"
|
|
31
|
+
/> — {{ t('role.access_rules_help_service') }}</li>
|
|
32
|
+
<li class="whitespace-nowrap"><Badge
|
|
33
|
+
value="aihub.admin.>"
|
|
34
|
+
severity="secondary"
|
|
35
|
+
size="small"
|
|
36
|
+
/> — {{ t('role.access_rules_help_admin') }}</li>
|
|
37
|
+
</ul>
|
|
38
|
+
</div>
|
|
39
|
+
</Popover>
|
|
40
|
+
</span>
|
|
41
|
+
<DataTable
|
|
42
|
+
v-if="rules.length"
|
|
43
|
+
:value="tableRows"
|
|
44
|
+
data-key="id"
|
|
45
|
+
size="small"
|
|
46
|
+
>
|
|
47
|
+
<Column field="accessRule">
|
|
48
|
+
<template #body="{ data }">
|
|
49
|
+
<Badge
|
|
50
|
+
:value="data.accessRule"
|
|
18
51
|
severity="secondary"
|
|
19
|
-
|
|
20
|
-
/>
|
|
21
|
-
|
|
22
|
-
|
|
52
|
+
class="border border-gray-400/30"
|
|
53
|
+
/>
|
|
54
|
+
</template>
|
|
55
|
+
</Column>
|
|
56
|
+
<Column class="w-24 !text-end">
|
|
57
|
+
<template #body="{ data }">
|
|
58
|
+
<Tag
|
|
59
|
+
v-if="isNew(data.accessRule)"
|
|
60
|
+
:value="t('role.is_new')"
|
|
61
|
+
severity="success"
|
|
62
|
+
/>
|
|
63
|
+
</template>
|
|
64
|
+
</Column>
|
|
65
|
+
<Column class="w-12">
|
|
66
|
+
<template #body="{ data }">
|
|
67
|
+
<Button
|
|
68
|
+
icon="pi pi-times"
|
|
23
69
|
severity="secondary"
|
|
70
|
+
variant="text"
|
|
71
|
+
rounded
|
|
24
72
|
size="small"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
73
|
+
@click="remove(data.accessRule)"
|
|
74
|
+
/>
|
|
75
|
+
</template>
|
|
76
|
+
</Column>
|
|
77
|
+
</DataTable>
|
|
78
|
+
<span
|
|
79
|
+
v-else
|
|
80
|
+
class="text-sm italic text-muted-color"
|
|
81
|
+
>
|
|
82
|
+
{{ t('role.no_access_rules') }}
|
|
83
|
+
</span>
|
|
84
|
+
<div class="flex items-center gap-2">
|
|
85
|
+
<Button
|
|
86
|
+
type="button"
|
|
87
|
+
icon="pi pi-th-large"
|
|
88
|
+
:label="t('role.presets_button')"
|
|
89
|
+
severity="secondary"
|
|
90
|
+
outlined
|
|
91
|
+
size="small"
|
|
92
|
+
@click="(e: Event) => presetsPopover?.toggle(e)"
|
|
93
|
+
/>
|
|
94
|
+
<InputText
|
|
95
|
+
v-model="newRule"
|
|
96
|
+
:placeholder="t('role.add_access_role')"
|
|
97
|
+
size="small"
|
|
98
|
+
class="flex-1"
|
|
99
|
+
@keyup.enter="add"
|
|
100
|
+
/>
|
|
101
|
+
<Button
|
|
102
|
+
type="button"
|
|
103
|
+
icon="pi pi-plus"
|
|
104
|
+
:label="t('role.add_button')"
|
|
105
|
+
size="small"
|
|
106
|
+
:disabled="!newRule"
|
|
107
|
+
@click="add"
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<Popover ref="presetsPopover">
|
|
112
|
+
<div class="flex max-h-96 w-80 flex-col gap-3 overflow-y-auto">
|
|
113
|
+
<span class="font-semibold">{{ t('role.presets_title') }}</span>
|
|
114
|
+
<div
|
|
115
|
+
v-for="group in presetGroups"
|
|
116
|
+
:key="group.category"
|
|
117
|
+
class="flex flex-col gap-1"
|
|
118
|
+
>
|
|
119
|
+
<span class="text-xs font-medium uppercase text-muted-color">{{ group.label }}</span>
|
|
120
|
+
<button
|
|
121
|
+
v-for="preset in group.presets"
|
|
122
|
+
:key="preset.rule"
|
|
123
|
+
type="button"
|
|
124
|
+
class="flex flex-col items-start gap-1 rounded-md p-2 text-left hover:bg-surface-100 disabled:opacity-40 dark:hover:bg-surface-800"
|
|
125
|
+
:disabled="rules.includes(preset.rule)"
|
|
126
|
+
@click="addPreset(preset.rule)"
|
|
127
|
+
>
|
|
128
|
+
<span class="flex items-center gap-2 text-sm font-medium">
|
|
129
|
+
{{ preset.name }}
|
|
130
|
+
<i
|
|
131
|
+
v-if="rules.includes(preset.rule)"
|
|
132
|
+
class="pi pi-check text-green-500"
|
|
133
|
+
/>
|
|
134
|
+
</span>
|
|
135
|
+
<span class="text-xs text-muted-color">{{ preset.description }}</span>
|
|
136
|
+
<Badge
|
|
137
|
+
:value="preset.rule"
|
|
138
|
+
severity="secondary"
|
|
139
|
+
size="small"
|
|
140
|
+
/>
|
|
141
|
+
</button>
|
|
142
|
+
</div>
|
|
37
143
|
</div>
|
|
38
144
|
</Popover>
|
|
39
|
-
</span>
|
|
40
|
-
<DataTable
|
|
41
|
-
v-if="rules.length"
|
|
42
|
-
:value="tableRows"
|
|
43
|
-
data-key="id"
|
|
44
|
-
size="small"
|
|
45
|
-
>
|
|
46
|
-
<Column field="accessRule">
|
|
47
|
-
<template #body="{ data }">
|
|
48
|
-
<Badge
|
|
49
|
-
:value="data.accessRule"
|
|
50
|
-
severity="secondary"
|
|
51
|
-
class="border border-gray-400/30"
|
|
52
|
-
/>
|
|
53
|
-
</template>
|
|
54
|
-
</Column>
|
|
55
|
-
<Column class="w-24 !text-end">
|
|
56
|
-
<template #body="{ data }">
|
|
57
|
-
<Tag
|
|
58
|
-
v-if="isNew(data.accessRule)"
|
|
59
|
-
:value="t('role.is_new')"
|
|
60
|
-
severity="success"
|
|
61
|
-
/>
|
|
62
|
-
</template>
|
|
63
|
-
</Column>
|
|
64
|
-
<Column class="w-12">
|
|
65
|
-
<template #body="{ data }">
|
|
66
|
-
<Button
|
|
67
|
-
icon="pi pi-times"
|
|
68
|
-
severity="secondary"
|
|
69
|
-
variant="text"
|
|
70
|
-
rounded
|
|
71
|
-
size="small"
|
|
72
|
-
@click="remove(data.accessRule)"
|
|
73
|
-
/>
|
|
74
|
-
</template>
|
|
75
|
-
</Column>
|
|
76
|
-
</DataTable>
|
|
77
|
-
<span
|
|
78
|
-
v-else
|
|
79
|
-
class="text-sm italic text-muted-color"
|
|
80
|
-
>
|
|
81
|
-
{{ t('role.no_access_rules') }}
|
|
82
|
-
</span>
|
|
83
|
-
<div class="flex items-center gap-2">
|
|
84
|
-
<InputText
|
|
85
|
-
v-model="newRule"
|
|
86
|
-
:placeholder="t('role.add_access_role')"
|
|
87
|
-
size="small"
|
|
88
|
-
class="flex-1"
|
|
89
|
-
@keyup.enter="add"
|
|
90
|
-
/>
|
|
91
|
-
<Button
|
|
92
|
-
type="button"
|
|
93
|
-
icon="pi pi-plus"
|
|
94
|
-
:label="t('role.add_button')"
|
|
95
|
-
size="small"
|
|
96
|
-
:disabled="!newRule"
|
|
97
|
-
@click="add"
|
|
98
|
-
/>
|
|
99
145
|
</div>
|
|
146
|
+
|
|
147
|
+
<AccessCapabilities
|
|
148
|
+
:rules="rules"
|
|
149
|
+
:restrict-to-tenant="restrictToTenant"
|
|
150
|
+
@add="addPreset"
|
|
151
|
+
@remove="remove"
|
|
152
|
+
/>
|
|
100
153
|
</div>
|
|
101
154
|
</template>
|
|
102
155
|
|
|
103
156
|
<script setup lang="ts">
|
|
104
157
|
import { useI18n } from 'vue-i18n'
|
|
105
158
|
|
|
159
|
+
import AccessCapabilities from './AccessCapabilities.vue'
|
|
160
|
+
|
|
161
|
+
import type { AccessPresetDto } from '@core/sdk/client'
|
|
162
|
+
|
|
163
|
+
import useAccessPresets from '@/composables/access/useAccessPresets'
|
|
164
|
+
|
|
106
165
|
const { t } = useI18n()
|
|
107
166
|
|
|
108
167
|
const accessRulesHelp = ref()
|
|
168
|
+
const presetsPopover = ref()
|
|
109
169
|
const rules = defineModel<string[]>('rules', { required: true })
|
|
110
170
|
|
|
111
|
-
const props = defineProps<{
|
|
171
|
+
const props = withDefaults(defineProps<{
|
|
112
172
|
initialRules: string[]
|
|
113
|
-
|
|
173
|
+
restrictToTenant?: boolean
|
|
174
|
+
}>(), {
|
|
175
|
+
restrictToTenant: true,
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
const { presets } = useAccessPresets()
|
|
179
|
+
|
|
180
|
+
const presetGroups = computed<{ category: string, label: string, presets: AccessPresetDto[] }[]>(() => {
|
|
181
|
+
const order = ['everything', 'agents', 'processes', 'knowledge']
|
|
182
|
+
const byCategory = new Map<string, AccessPresetDto[]>()
|
|
183
|
+
for (const preset of presets.value ?? []) {
|
|
184
|
+
if (!byCategory.has(preset.category)) byCategory.set(preset.category, [])
|
|
185
|
+
byCategory.get(preset.category)!.push(preset)
|
|
186
|
+
}
|
|
187
|
+
return [...byCategory.keys()]
|
|
188
|
+
.sort((a, b) => order.indexOf(a) - order.indexOf(b))
|
|
189
|
+
.map(category => ({ category, label: t(`role.preset_category_${category}`), presets: byCategory.get(category)! }))
|
|
190
|
+
})
|
|
114
191
|
|
|
115
192
|
const tableRows = computed(() =>
|
|
116
193
|
rules.value.map(accessRule => ({ accessRule, id: accessRule })),
|
|
@@ -122,10 +199,14 @@ const isNew = (rule: string) => !props.initialRules.includes(rule)
|
|
|
122
199
|
|
|
123
200
|
const add = () => {
|
|
124
201
|
if (!newRule.value) return
|
|
125
|
-
rules.value.push(newRule.value)
|
|
202
|
+
if (!rules.value.includes(newRule.value)) rules.value.push(newRule.value)
|
|
126
203
|
newRule.value = ''
|
|
127
204
|
}
|
|
128
205
|
|
|
206
|
+
const addPreset = (rule: string) => {
|
|
207
|
+
if (!rules.value.includes(rule)) rules.value.push(rule)
|
|
208
|
+
}
|
|
209
|
+
|
|
129
210
|
const remove = (rule: string) => {
|
|
130
211
|
rules.value = rules.value.filter(r => r !== rule)
|
|
131
212
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type AccessCapabilitiesResponse, getAccessCapabilities } from '@core/sdk/client'
|
|
2
|
+
|
|
3
|
+
import type { MaybeRefOrGetter } from 'vue'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Evaluates the capability catalog (services / agents / processes) against a draft rule set.
|
|
7
|
+
*
|
|
8
|
+
* Keyed on the rules so it refetches whenever they change — ticking a capability adds its exact rule,
|
|
9
|
+
* which re-evaluates the whole catalog (e.g. a broad rule then locks the capabilities it covers).
|
|
10
|
+
*/
|
|
11
|
+
export function useAccessCapabilities(
|
|
12
|
+
rules: MaybeRefOrGetter<string[]>,
|
|
13
|
+
restrictToTenant: MaybeRefOrGetter<boolean> = true,
|
|
14
|
+
// The read-only user view passes the viewed user's AIHubSysAdmin flag: a sysadmin holds admin on
|
|
15
|
+
// everything via the short-circuit, not via rules, so the catalog must be evaluated with it set.
|
|
16
|
+
isSysAdmin: MaybeRefOrGetter<boolean> = false,
|
|
17
|
+
) {
|
|
18
|
+
const { tenantId } = useTenant()
|
|
19
|
+
// The tenant-ceiling editor (configure-new-tenant) runs on a route with no tenant param. With
|
|
20
|
+
// restrict_to_tenant=false the catalog is tenant-independent, so 'active' is a safe path fallback.
|
|
21
|
+
const targetTenantId = computed(() => tenantId.value ?? 'active')
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
data: capabilities,
|
|
25
|
+
isPending: capabilitiesAreLoading,
|
|
26
|
+
} = useQuery<AccessCapabilitiesResponse>({
|
|
27
|
+
key: () => [
|
|
28
|
+
'tenant', targetTenantId.value, 'access-capabilities',
|
|
29
|
+
toValue(restrictToTenant), toValue(isSysAdmin), JSON.stringify(toValue(rules)),
|
|
30
|
+
],
|
|
31
|
+
staleTime: 0,
|
|
32
|
+
query: async () => {
|
|
33
|
+
return await getAccessCapabilities({
|
|
34
|
+
composable: '$fetch',
|
|
35
|
+
path: { tenant_id: targetTenantId.value },
|
|
36
|
+
body: {
|
|
37
|
+
access_rules: toValue(rules),
|
|
38
|
+
restrict_to_tenant: toValue(restrictToTenant),
|
|
39
|
+
is_sys_admin: toValue(isSysAdmin),
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
capabilities,
|
|
47
|
+
capabilitiesAreLoading,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type AccessPresetDto, getAccessPresets } from '@core/sdk/client'
|
|
2
|
+
import { minutesToMilliseconds } from 'date-fns'
|
|
3
|
+
|
|
4
|
+
export default defineQuery(() => {
|
|
5
|
+
const { tenantId } = useTenant()
|
|
6
|
+
// Presets are tenant-independent; fall back to 'active' so the editor works on the
|
|
7
|
+
// configure-new-tenant route (which has no tenant param). See useAccessCapabilities.
|
|
8
|
+
const targetTenantId = computed(() => tenantId.value ?? 'active')
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
data: presets,
|
|
12
|
+
isPending: presetsAreLoading,
|
|
13
|
+
} = useQuery<AccessPresetDto[]>({
|
|
14
|
+
key: () => ['tenant', targetTenantId.value, 'access-presets'],
|
|
15
|
+
staleTime: minutesToMilliseconds(30),
|
|
16
|
+
query: async () => {
|
|
17
|
+
return await getAccessPresets({
|
|
18
|
+
composable: '$fetch',
|
|
19
|
+
path: { tenant_id: targetTenantId.value },
|
|
20
|
+
})
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
presets,
|
|
26
|
+
presetsAreLoading,
|
|
27
|
+
}
|
|
28
|
+
})
|
package/i18n/locales/de.yaml
CHANGED
|
@@ -631,6 +631,23 @@ role:
|
|
|
631
631
|
access_rules_help_agent_instances: Alle Instanzen von MyAgent
|
|
632
632
|
access_rules_help_service: Knowledge-Service
|
|
633
633
|
access_rules_help_admin: Admin-Zugriff auf alles
|
|
634
|
+
presets_button: Vorlagen
|
|
635
|
+
presets_title: Zugriffsvorlagen
|
|
636
|
+
preset_category_everything: Alles
|
|
637
|
+
preset_category_agents: Agenten
|
|
638
|
+
preset_category_processes: Prozesse
|
|
639
|
+
preset_category_knowledge: Wissen
|
|
640
|
+
capabilities_title: Was diese Regeln gewähren
|
|
641
|
+
capabilities_subtitle: Aktiviere eine Fähigkeit, um sie zu gewähren. Gesperrte Einträge
|
|
642
|
+
sind durch eine breitere Regel abgedeckt.
|
|
643
|
+
capabilities_readonly_title: Effektiver Zugriff
|
|
644
|
+
capabilities_readonly_subtitle: Was dieser Benutzer tun kann, basierend auf seinen
|
|
645
|
+
Rollen und innerhalb der Grenzen des Tenants.
|
|
646
|
+
capabilities_empty: Dieser Benutzer hat keinen Zugriff.
|
|
647
|
+
capability_locked: Durch eine breitere Regel gewährt — entferne diese Regel, um
|
|
648
|
+
dies zu ändern.
|
|
649
|
+
capability_readonly: Keine einzelne Regel gewährt dies allein — nutze eine Vorlage
|
|
650
|
+
oder eine breitere Regel.
|
|
634
651
|
pattern_help_intro: 'Muster verwenden Platzhalter für Agenten:'
|
|
635
652
|
pattern_help_all: Alle Agenten
|
|
636
653
|
pattern_help_wildcard: Alle Instanzen von MyAgent
|
package/i18n/locales/en.yaml
CHANGED
|
@@ -621,6 +621,22 @@ role:
|
|
|
621
621
|
access_rules_help_agent_instances: All instances of MyAgent
|
|
622
622
|
access_rules_help_service: Knowledge service
|
|
623
623
|
access_rules_help_admin: Admin access to everything
|
|
624
|
+
presets_button: Presets
|
|
625
|
+
presets_title: Access presets
|
|
626
|
+
preset_category_everything: Everything
|
|
627
|
+
preset_category_agents: Agents
|
|
628
|
+
preset_category_processes: Processes
|
|
629
|
+
preset_category_knowledge: Knowledge
|
|
630
|
+
capabilities_title: What these rules grant
|
|
631
|
+
capabilities_subtitle: Tick a capability to grant it. Locked items are covered by
|
|
632
|
+
a broader rule.
|
|
633
|
+
capabilities_readonly_title: Effective access
|
|
634
|
+
capabilities_readonly_subtitle: What this user can do, based on their roles and
|
|
635
|
+
within the tenant's limits.
|
|
636
|
+
capabilities_empty: This user has no access.
|
|
637
|
+
capability_locked: Granted by a broader rule — remove that rule to change this.
|
|
638
|
+
capability_readonly: No single rule grants this on its own — use a preset or a broader
|
|
639
|
+
rule.
|
|
624
640
|
pattern_help_intro: 'Patterns use wildcards to match agents:'
|
|
625
641
|
pattern_help_all: All agents
|
|
626
642
|
pattern_help_wildcard: All instances of MyAgent
|
package/i18n/locales/fr.yaml
CHANGED
|
@@ -629,6 +629,23 @@ role:
|
|
|
629
629
|
access_rules_help_agent_instances: Toutes les instances de MyAgent
|
|
630
630
|
access_rules_help_service: Service Knowledge
|
|
631
631
|
access_rules_help_admin: Accès admin à tout
|
|
632
|
+
presets_button: Modèles
|
|
633
|
+
presets_title: Modèles d'accès
|
|
634
|
+
preset_category_everything: Tout
|
|
635
|
+
preset_category_agents: Agents
|
|
636
|
+
preset_category_processes: Processus
|
|
637
|
+
preset_category_knowledge: Connaissances
|
|
638
|
+
capabilities_title: Ce que ces règles accordent
|
|
639
|
+
capabilities_subtitle: Cochez une capacité pour l'accorder. Les éléments verrouillés
|
|
640
|
+
sont couverts par une règle plus large.
|
|
641
|
+
capabilities_readonly_title: Accès effectif
|
|
642
|
+
capabilities_readonly_subtitle: Ce que cet utilisateur peut faire, selon ses rôles
|
|
643
|
+
et dans les limites du tenant.
|
|
644
|
+
capabilities_empty: Cet utilisateur n'a aucun accès.
|
|
645
|
+
capability_locked: Accordé par une règle plus large — supprimez cette règle pour
|
|
646
|
+
changer ceci.
|
|
647
|
+
capability_readonly: Aucune règle unique ne l'accorde — utilisez un modèle ou une
|
|
648
|
+
règle plus large.
|
|
632
649
|
pattern_help_intro: 'Les modèles utilisent des jokers pour les agents :'
|
|
633
650
|
pattern_help_all: Tous les agents
|
|
634
651
|
pattern_help_wildcard: Toutes les instances de MyAgent
|
package/i18n/locales/it.yaml
CHANGED
|
@@ -626,6 +626,23 @@ role:
|
|
|
626
626
|
access_rules_help_agent_instances: Tutte le istanze di MyAgent
|
|
627
627
|
access_rules_help_service: Servizio Knowledge
|
|
628
628
|
access_rules_help_admin: Accesso admin a tutto
|
|
629
|
+
presets_button: Modelli
|
|
630
|
+
presets_title: Modelli di accesso
|
|
631
|
+
preset_category_everything: Tutto
|
|
632
|
+
preset_category_agents: Agenti
|
|
633
|
+
preset_category_processes: Processi
|
|
634
|
+
preset_category_knowledge: Conoscenza
|
|
635
|
+
capabilities_title: Cosa concedono queste regole
|
|
636
|
+
capabilities_subtitle: Seleziona una capacità per concederla. Gli elementi bloccati
|
|
637
|
+
sono coperti da una regola più ampia.
|
|
638
|
+
capabilities_readonly_title: Accesso effettivo
|
|
639
|
+
capabilities_readonly_subtitle: Cosa può fare questo utente, in base ai suoi ruoli
|
|
640
|
+
e nei limiti del tenant.
|
|
641
|
+
capabilities_empty: Questo utente non ha accesso.
|
|
642
|
+
capability_locked: Concesso da una regola più ampia — rimuovi quella regola per
|
|
643
|
+
cambiare questo.
|
|
644
|
+
capability_readonly: Nessuna singola regola lo concede — usa un modello o una regola
|
|
645
|
+
più ampia.
|
|
629
646
|
pattern_help_intro: 'I modelli usano caratteri jolly per gli agenti:'
|
|
630
647
|
pattern_help_all: Tutti gli agenti
|
|
631
648
|
pattern_help_wildcard: Tutte le istanze di MyAgent
|