mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-11-09 17:06:13 -06:00
feat(xo6/privilege): add scope sub-page to the privilege detail page (#10655)
* feat(xo6/privilege): add scope sub-page to the privilege detail page The Scope sub-page shows which objects a privilege applies to: its scope type, the query of a dynamic selection, and the table of the objects it currently applies to, whether selected manually, matched by the query or covering all the objects of its resource. See XO-2948 * fix(xo6/role): read the privilege scope type from its resource in the privilege scope page * fix(xo6/role): show the error state of the objects a privilege currently applies to * feat(xo6/privilege): show the full objects table in the privilege scope page As in the mockups, the objects a privilege currently applies to are listed in the same table as in its creation drawer (columns of the resource, search engine), read-only, instead of a list of names.
This commit is contained in:
committed by
GitHub
parent
264e0db674
commit
c35d56c057
@@ -37,8 +37,10 @@ function renderBodyCheckbox({ selected, onToggle, disabled }: CheckboxCellBindin
|
||||
}
|
||||
|
||||
export const useCheckboxColumn = defineColumn(() => ({
|
||||
renderHead: (model: Ref<boolean | undefined>, options?: CheckboxHeadCellOptions) =>
|
||||
h(VtsHeaderCell, { style: { width: COLUMN_WIDTH } }, () => renderHeadCheckbox(model, options)),
|
||||
renderHead: (model?: Ref<boolean | undefined>, options?: CheckboxHeadCellOptions) =>
|
||||
h(VtsHeaderCell, { style: { width: COLUMN_WIDTH } }, () =>
|
||||
model === undefined ? undefined : renderHeadCheckbox(model, options)
|
||||
),
|
||||
renderBody: (bindings?: CheckboxCellBindings) =>
|
||||
h(UiTableCell, { style: { width: COLUMN_WIDTH } }, () =>
|
||||
bindings === undefined ? undefined : renderBodyCheckbox(bindings)
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
v-if="formData.scope.scopeType === 'manual-selection' && selectableResource !== undefined"
|
||||
class="selector"
|
||||
>
|
||||
<PrivilegeScopeManualSelection
|
||||
<PrivilegeScopeResourceTable
|
||||
v-model:selected-ids="formData.scope.selectedIds"
|
||||
:resource="selectableResource"
|
||||
/>
|
||||
@@ -132,9 +132,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import PrivilegeScopeManualSelection from '@/modules/role/components/drawer/scope/PrivilegeScopeManualSelection.vue'
|
||||
import PrivilegeScopeObjectsList from '@/modules/role/components/drawer/scope/PrivilegeScopeObjectsList.vue'
|
||||
import PrivilegeScopePreviewTable from '@/modules/role/components/drawer/scope/PrivilegeScopePreviewTable.vue'
|
||||
import PrivilegeScopeResourceTable from '@/modules/role/components/drawer/scope/PrivilegeScopeResourceTable.vue'
|
||||
import RoleFormSelect from '@/modules/role/components/form/inputs/RoleFormSelect.vue'
|
||||
import { usePrivilegeScopeTypeLabels } from '@/modules/role/composables/use-privilege-scope-type-labels.composable.ts'
|
||||
import { useNewPrivilegeForm } from '@/modules/role/form/new-privilege/use-new-privilege-form.ts'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="privilege-scope-hosts-table">
|
||||
<VtsQueryBuilder :model-value="filter" :schema @search="filter = $event" />
|
||||
<VtsTable :state :pagination-bindings :selection-bindings>
|
||||
<VtsTable :state :pagination-bindings :selection-bindings="readonly ? undefined : selectionBindings">
|
||||
<thead>
|
||||
<tr>
|
||||
<HeadCells />
|
||||
@@ -34,12 +34,21 @@ import { logicAnd, logicNot, logicOr } from '@vueuse/math'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const selectedIds = defineModel<string[]>('selectedIds', { required: true })
|
||||
const { visibleIds, readonly = false } = defineProps<{
|
||||
visibleIds?: string[]
|
||||
readonly?: boolean
|
||||
}>()
|
||||
|
||||
const selectedIds = defineModel<string[]>('selectedIds', { default: () => [] })
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { hosts, areHostsReady, hasHostFetchError, isMasterHost } = useXoHostCollection()
|
||||
|
||||
const visibleHosts = computed(() =>
|
||||
visibleIds === undefined ? hosts.value : hosts.value.filter(host => visibleIds.includes(host.id))
|
||||
)
|
||||
|
||||
const { pifsByHost, arePifsReady, hasPifFetchError } = useXoPifCollection()
|
||||
|
||||
const isReady = logicAnd(areHostsReady, arePifsReady)
|
||||
@@ -48,7 +57,7 @@ const {
|
||||
items: filteredHosts,
|
||||
filter,
|
||||
schema,
|
||||
} = useHostQueryBuilder('privilege-scope-hosts', hosts, {
|
||||
} = useHostQueryBuilder('privilege-scope-hosts', visibleHosts, {
|
||||
ignoreRoute: true,
|
||||
})
|
||||
|
||||
@@ -56,7 +65,11 @@ const state = useTableState({
|
||||
busy: logicNot(isReady),
|
||||
error: logicOr(hasHostFetchError, hasPifFetchError),
|
||||
empty: () =>
|
||||
hosts.value.length === 0 ? t('no-host-detected') : filteredHosts.value.length === 0 ? { type: 'no-result' } : false,
|
||||
visibleHosts.value.length === 0
|
||||
? t('no-host-detected')
|
||||
: filteredHosts.value.length === 0
|
||||
? { type: 'no-result' }
|
||||
: false,
|
||||
})
|
||||
|
||||
const { pageRecords: paginatedHosts, paginationBindings } = usePagination('privilege-scope-hosts', filteredHosts, {
|
||||
@@ -65,7 +78,7 @@ const { pageRecords: paginatedHosts, paginationBindings } = usePagination('privi
|
||||
|
||||
const { pageSelectionModel, isSelected, toggleSelection, selectionBindings } = usePrivilegeScopeSelection({
|
||||
selectedIds,
|
||||
items: hosts,
|
||||
items: visibleHosts,
|
||||
filteredItems: filteredHosts,
|
||||
pageItems: paginatedHosts,
|
||||
isReady,
|
||||
@@ -85,7 +98,7 @@ function getMasterIcon(host: FrontXoHost) {
|
||||
const { HeadCells, BodyCells } = useHostColumns({
|
||||
exclude: ['srStatus', 'selectItem'],
|
||||
head: () => ({
|
||||
checkbox: r => r(pageSelectionModel),
|
||||
checkbox: r => (readonly ? r() : r(pageSelectionModel)),
|
||||
}),
|
||||
body: (host: FrontXoHost) => {
|
||||
const ipAddresses = computed(() => getHostIpAddresses(host.address, pifsByHost.value.get(host.id)))
|
||||
@@ -93,7 +106,7 @@ const { HeadCells, BodyCells } = useHostColumns({
|
||||
const rightIcon = computed(() => getMasterIcon(host))
|
||||
|
||||
return {
|
||||
checkbox: r => r({ selected: isSelected(host.id), onToggle: () => toggleSelection(host.id) }),
|
||||
checkbox: r => (readonly ? r() : r({ selected: isSelected(host.id), onToggle: () => toggleSelection(host.id) })),
|
||||
host: r =>
|
||||
r({
|
||||
label: host.name_label,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="privilege-scope-objects-table">
|
||||
<VtsQueryBuilder :model-value="filter" :schema @search="filter = $event" />
|
||||
<VtsTable :state :pagination-bindings :selection-bindings>
|
||||
<VtsTable :state :pagination-bindings :selection-bindings="readonly ? undefined : selectionBindings">
|
||||
<thead>
|
||||
<tr>
|
||||
<HeadCells />
|
||||
@@ -30,15 +30,23 @@ import { usePrivilegeScopeObjectColumns } from '@core/tables/column-sets/privile
|
||||
import { useStringSchema } from '@core/utils/query-builder/use-string-schema.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { tableId, objects, isReady, hasError, emptyMessage } = defineProps<{
|
||||
const {
|
||||
tableId,
|
||||
objects,
|
||||
isReady,
|
||||
hasError,
|
||||
emptyMessage,
|
||||
readonly = false,
|
||||
} = defineProps<{
|
||||
tableId: string
|
||||
objects: PrivilegeScopeObject[]
|
||||
isReady: boolean
|
||||
hasError: boolean
|
||||
emptyMessage: string
|
||||
readonly?: boolean
|
||||
}>()
|
||||
|
||||
const selectedIds = defineModel<string[]>('selectedIds', { required: true })
|
||||
const selectedIds = defineModel<string[]>('selectedIds', { default: () => [] })
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -70,10 +78,11 @@ const { pageSelectionModel, isSelected, toggleSelection, selectionBindings } = u
|
||||
|
||||
const { HeadCells, BodyCells } = usePrivilegeScopeObjectColumns({
|
||||
head: () => ({
|
||||
checkbox: r => r(pageSelectionModel),
|
||||
checkbox: r => (readonly ? r() : r(pageSelectionModel)),
|
||||
}),
|
||||
body: (object: PrivilegeScopeObject) => ({
|
||||
checkbox: r => r({ selected: isSelected(object.id), onToggle: () => toggleSelection(object.id) }),
|
||||
checkbox: r =>
|
||||
readonly ? r() : r({ selected: isSelected(object.id), onToggle: () => toggleSelection(object.id) }),
|
||||
object: r => r({ label: object.label, to: object.to, icon: object.icon }),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<template>
|
||||
<PrivilegeScopeVmsTable v-if="resource === 'vm'" v-model:selected-ids="selectedIds" />
|
||||
<PrivilegeScopeHostsTable v-else-if="resource === 'host'" v-model:selected-ids="selectedIds" />
|
||||
<PrivilegeScopeSrsTable v-else-if="resource === 'sr'" v-model:selected-ids="selectedIds" />
|
||||
<PrivilegeScopeVmsTable v-if="resource === 'vm'" v-model:selected-ids="selectedIds" :visible-ids :readonly />
|
||||
<PrivilegeScopeHostsTable v-else-if="resource === 'host'" v-model:selected-ids="selectedIds" :visible-ids :readonly />
|
||||
<PrivilegeScopeSrsTable v-else-if="resource === 'sr'" v-model:selected-ids="selectedIds" :visible-ids :readonly />
|
||||
<PrivilegeScopeObjectsTable
|
||||
v-else
|
||||
:key="resource"
|
||||
v-model:selected-ids="selectedIds"
|
||||
:table-id="`privilege-scope-${resource}`"
|
||||
:objects
|
||||
:objects="visibleObjects"
|
||||
:is-ready
|
||||
:has-error
|
||||
:empty-message
|
||||
:readonly
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -24,16 +25,26 @@ import type { SelectablePrivilegeResource } from '@/modules/role/utils/privilege
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { resource } = defineProps<{
|
||||
const {
|
||||
resource,
|
||||
visibleIds,
|
||||
readonly = false,
|
||||
} = defineProps<{
|
||||
resource: SelectablePrivilegeResource
|
||||
visibleIds?: string[]
|
||||
readonly?: boolean
|
||||
}>()
|
||||
|
||||
const selectedIds = defineModel<string[]>('selectedIds', { required: true })
|
||||
const selectedIds = defineModel<string[]>('selectedIds', { default: () => [] })
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { objects, isReady, hasError } = usePrivilegeScopeObjects(() => resource)
|
||||
|
||||
const visibleObjects = computed(() =>
|
||||
visibleIds === undefined ? objects.value : objects.value.filter(object => visibleIds.includes(object.id))
|
||||
)
|
||||
|
||||
const emptyMessage = computed(() => {
|
||||
if (resource === 'pool') {
|
||||
return t('no-pools-detected')
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="privilege-scope-srs-table">
|
||||
<VtsQueryBuilder :model-value="filter" :schema @search="filter = $event" />
|
||||
<VtsTable :state :pagination-bindings :selection-bindings>
|
||||
<VtsTable :state :pagination-bindings :selection-bindings="readonly ? undefined : selectionBindings">
|
||||
<thead>
|
||||
<tr>
|
||||
<HeadCells />
|
||||
@@ -37,7 +37,12 @@ import { logicNot } from '@vueuse/math'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const selectedIds = defineModel<string[]>('selectedIds', { required: true })
|
||||
const { visibleIds, readonly = false } = defineProps<{
|
||||
visibleIds?: string[]
|
||||
readonly?: boolean
|
||||
}>()
|
||||
|
||||
const selectedIds = defineModel<string[]>('selectedIds', { default: () => [] })
|
||||
|
||||
const SCOPE: SrScope = { type: SR_SCOPE_TYPE.POOL }
|
||||
|
||||
@@ -45,11 +50,15 @@ const { t } = useI18n()
|
||||
|
||||
const { srs, areSrsReady, hasSrFetchError, isDefaultSr } = useXoSrCollection()
|
||||
|
||||
const visibleSrs = computed(() =>
|
||||
visibleIds === undefined ? srs.value : srs.value.filter(sr => visibleIds.includes(sr.id))
|
||||
)
|
||||
|
||||
const {
|
||||
items: filteredSrs,
|
||||
filter,
|
||||
schema,
|
||||
} = useSrQueryBuilder('privilege-scope-srs', srs, {
|
||||
} = useSrQueryBuilder('privilege-scope-srs', visibleSrs, {
|
||||
ignoreRoute: true,
|
||||
})
|
||||
|
||||
@@ -57,7 +66,7 @@ const state = useTableState({
|
||||
busy: logicNot(areSrsReady),
|
||||
error: hasSrFetchError,
|
||||
empty: () =>
|
||||
srs.value.length === 0
|
||||
visibleSrs.value.length === 0
|
||||
? t('no-storage-repository-detected')
|
||||
: filteredSrs.value.length === 0
|
||||
? { type: 'no-result' }
|
||||
@@ -70,7 +79,7 @@ const { pageRecords: paginatedSrs, paginationBindings } = usePagination('privile
|
||||
|
||||
const { pageSelectionModel, isSelected, toggleSelection, selectionBindings } = usePrivilegeScopeSelection({
|
||||
selectedIds,
|
||||
items: srs,
|
||||
items: visibleSrs,
|
||||
filteredItems: filteredSrs,
|
||||
pageItems: paginatedSrs,
|
||||
isReady: areSrsReady,
|
||||
@@ -90,7 +99,7 @@ function getPrimaryIcon(sr: FrontXoSr) {
|
||||
const { HeadCells, BodyCells } = useSrColumns({
|
||||
exclude: ['actions'],
|
||||
head: () => ({
|
||||
checkbox: r => r(pageSelectionModel),
|
||||
checkbox: r => (readonly ? r() : r(pageSelectionModel)),
|
||||
}),
|
||||
body: (sr: FrontXoSr) => {
|
||||
const rightIcon = computed(() => getPrimaryIcon(sr))
|
||||
@@ -98,7 +107,7 @@ const { HeadCells, BodyCells } = useSrColumns({
|
||||
const { srStatusIcon, getSrAccessModeLabel } = useXoSrUtils(sr, () => SCOPE)
|
||||
|
||||
return {
|
||||
checkbox: r => r({ selected: isSelected(sr.id), onToggle: () => toggleSelection(sr.id) }),
|
||||
checkbox: r => (readonly ? r() : r({ selected: isSelected(sr.id), onToggle: () => toggleSelection(sr.id) })),
|
||||
storageRepository: r =>
|
||||
r({
|
||||
label: sr.name_label,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="privilege-scope-vms-table">
|
||||
<VtsQueryBuilder :model-value="filter" :schema @search="filter = $event" />
|
||||
<VtsTable :state :pagination-bindings :selection-bindings>
|
||||
<VtsTable :state :pagination-bindings :selection-bindings="readonly ? undefined : selectionBindings">
|
||||
<thead>
|
||||
<tr>
|
||||
<HeadCells />
|
||||
@@ -31,18 +31,27 @@ import { logicNot } from '@vueuse/math'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const selectedIds = defineModel<string[]>('selectedIds', { required: true })
|
||||
const { visibleIds, readonly = false } = defineProps<{
|
||||
visibleIds?: string[]
|
||||
readonly?: boolean
|
||||
}>()
|
||||
|
||||
const selectedIds = defineModel<string[]>('selectedIds', { default: () => [] })
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { vms, areVmsReady, hasVmFetchError } = useXoVmCollection()
|
||||
|
||||
const visibleVms = computed(() =>
|
||||
visibleIds === undefined ? vms.value : vms.value.filter(vm => visibleIds.includes(vm.id))
|
||||
)
|
||||
|
||||
const {
|
||||
items: filteredVms,
|
||||
filter,
|
||||
schema,
|
||||
getDisplayData,
|
||||
} = useVmQueryBuilder('privilege-scope-vms', vms, {
|
||||
} = useVmQueryBuilder('privilege-scope-vms', visibleVms, {
|
||||
ignoreRoute: true,
|
||||
})
|
||||
|
||||
@@ -50,7 +59,11 @@ const state = useTableState({
|
||||
busy: logicNot(areVmsReady),
|
||||
error: hasVmFetchError,
|
||||
empty: () =>
|
||||
vms.value.length === 0 ? t('no-vm-detected') : filteredVms.value.length === 0 ? { type: 'no-result' } : false,
|
||||
visibleVms.value.length === 0
|
||||
? t('no-vm-detected')
|
||||
: filteredVms.value.length === 0
|
||||
? { type: 'no-result' }
|
||||
: false,
|
||||
})
|
||||
|
||||
const displayVms = computed(() => filteredVms.value.map(vm => getDisplayData(vm)))
|
||||
@@ -61,7 +74,7 @@ const { pageRecords: paginatedVms, paginationBindings } = usePagination('privile
|
||||
|
||||
const { pageSelectionModel, isSelected, toggleSelection, selectionBindings } = usePrivilegeScopeSelection({
|
||||
selectedIds,
|
||||
items: vms,
|
||||
items: visibleVms,
|
||||
filteredItems: filteredVms,
|
||||
pageItems: paginatedVms,
|
||||
isReady: areVmsReady,
|
||||
@@ -70,10 +83,10 @@ const { pageSelectionModel, isSelected, toggleSelection, selectionBindings } = u
|
||||
const { HeadCells, BodyCells } = useVmColumns({
|
||||
exclude: ['actions'],
|
||||
head: () => ({
|
||||
checkbox: r => r(pageSelectionModel),
|
||||
checkbox: r => (readonly ? r() : r(pageSelectionModel)),
|
||||
}),
|
||||
body: (vm: VmDisplayData) => ({
|
||||
checkbox: r => r({ selected: isSelected(vm.id), onToggle: () => toggleSelection(vm.id) }),
|
||||
checkbox: r => (readonly ? r() : r({ selected: isSelected(vm.id), onToggle: () => toggleSelection(vm.id) })),
|
||||
vm: r => r({ label: vm.name_label, to: { name: '/vm/[id]/dashboard', params: { id: vm.id } }, icon: vm.vmIcon }),
|
||||
ipAddresses: r => r(vm.ipAddresses),
|
||||
vcpus: r => r(vm.CPUs.number),
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
>
|
||||
{{ t('general') }}
|
||||
</UiMenuItem>
|
||||
<UiMenuItem
|
||||
:size
|
||||
:to="{ name: '/admin/privilege/[id]/scope', params: { id: privilege.id } }"
|
||||
:selected="route.name === '/admin/privilege/[id]/scope'"
|
||||
>
|
||||
{{ t('privilege:scope') }}
|
||||
</UiMenuItem>
|
||||
</template>
|
||||
<slot />
|
||||
</DetailPageLayout>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<UiCard>
|
||||
<UiTitle>
|
||||
{{ t('privilege:scope') }}
|
||||
</UiTitle>
|
||||
<VtsTabularKeyValueList>
|
||||
<VtsTabularKeyValueRow :label="t('privilege:scope-type')" :value="scopeTypeLabels[scopeType]" />
|
||||
<VtsTabularKeyValueRow
|
||||
v-if="scopeType === 'dynamic-selection' && privilege.selector !== undefined"
|
||||
:label="t('privilege:selector')"
|
||||
>
|
||||
<template #value>
|
||||
<UiLogEntryViewer :content="privilege.selector" size="medium" accent="info" />
|
||||
</template>
|
||||
</VtsTabularKeyValueRow>
|
||||
</VtsTabularKeyValueList>
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { usePrivilegeScopeTypeLabels } from '@/modules/role/composables/use-privilege-scope-type-labels.composable.ts'
|
||||
import { getPrivilegeScopeType } from '@/modules/role/utils/privilege-scope.util.ts'
|
||||
import VtsTabularKeyValueList from '@core/components/tabular-key-value-list/VtsTabularKeyValueList.vue'
|
||||
import VtsTabularKeyValueRow from '@core/components/tabular-key-value-row/VtsTabularKeyValueRow.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiLogEntryViewer from '@core/components/ui/log-entry-viewer/UiLogEntryViewer.vue'
|
||||
import UiTitle from '@core/components/ui/title/UiTitle.vue'
|
||||
import type { XoAclBasePrivilege } from '@vates/types'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { privilege } = defineProps<{
|
||||
privilege: XoAclBasePrivilege
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const scopeTypeLabels = usePrivilegeScopeTypeLabels()
|
||||
|
||||
const scopeType = computed(() => getPrivilegeScopeType(privilege.resource, privilege.selector))
|
||||
</script>
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<UiCard>
|
||||
<UiTitle>
|
||||
{{ t('privilege:currently-applied-to') }}
|
||||
</UiTitle>
|
||||
<VtsStateHero v-if="hasAppliedToIdsError" type="error" format="card" size="small">
|
||||
{{ t('error-no-data') }}
|
||||
</VtsStateHero>
|
||||
<VtsStateHero v-else-if="appliedToIds === undefined" type="busy" format="card" size="small" />
|
||||
<VtsStateHero v-else-if="appliedToIds.length === 0" type="no-data" format="card" size="small">
|
||||
{{ t('privilege:no-object') }}
|
||||
</VtsStateHero>
|
||||
<PrivilegeScopeResourceTable v-else :resource :visible-ids="appliedToIds" readonly />
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import PrivilegeScopeResourceTable from '@/modules/role/components/drawer/scope/PrivilegeScopeResourceTable.vue'
|
||||
import { usePrivilegeAppliedToIds } from '@/modules/role/composables/use-privilege-applied-to-ids.composable.ts'
|
||||
import type { SelectablePrivilegeResource } from '@/modules/role/utils/privilege-catalogue.util.ts'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiTitle from '@core/components/ui/title/UiTitle.vue'
|
||||
import type { XoAclBasePrivilege } from '@vates/types'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { privilege } = defineProps<{
|
||||
privilege: XoAclBasePrivilege
|
||||
resource: SelectablePrivilegeResource
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { appliedToIds, hasAppliedToIdsError } = usePrivilegeAppliedToIds(() => privilege)
|
||||
</script>
|
||||
33
@xen-orchestra/web/src/pages/admin/privilege/[id]/scope.vue
Normal file
33
@xen-orchestra/web/src/pages/admin/privilege/[id]/scope.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<PrivilegeDetailLayout :privilege>
|
||||
<div class="cards">
|
||||
<PrivilegeScopeDetailsCard :privilege />
|
||||
<PrivilegeScopeObjectsCard v-if="selectableResource !== undefined" :privilege :resource="selectableResource" />
|
||||
</div>
|
||||
</PrivilegeDetailLayout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import PrivilegeDetailLayout from '@/modules/role/components/privilege/detail/PrivilegeDetailLayout.vue'
|
||||
import PrivilegeScopeDetailsCard from '@/modules/role/components/privilege/detail/PrivilegeScopeDetailsCard.vue'
|
||||
import PrivilegeScopeObjectsCard from '@/modules/role/components/privilege/detail/PrivilegeScopeObjectsCard.vue'
|
||||
import { isSelectablePrivilegeResource } from '@/modules/role/utils/privilege-catalogue.util.ts'
|
||||
import type { XoAclBasePrivilege } from '@vates/types'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { privilege } = defineProps<{
|
||||
privilege: XoAclBasePrivilege
|
||||
}>()
|
||||
|
||||
const selectableResource = computed(() =>
|
||||
isSelectablePrivilegeResource(privilege.resource) ? privilege.resource : undefined
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
</style>
|
||||
15
@xen-orchestra/web/src/route-map.d.ts
vendored
15
@xen-orchestra/web/src/route-map.d.ts
vendored
@@ -177,6 +177,7 @@ declare module 'vue-router/auto-routes' {
|
||||
{ id: ParamValue<true> },
|
||||
{ id: ParamValue<false> },
|
||||
| '/admin/privilege/[id]/general'
|
||||
| '/admin/privilege/[id]/scope'
|
||||
>,
|
||||
'/admin/privilege/[id]/general': RouteRecordInfo<
|
||||
'/admin/privilege/[id]/general',
|
||||
@@ -185,6 +186,13 @@ declare module 'vue-router/auto-routes' {
|
||||
{ id: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/admin/privilege/[id]/scope': RouteRecordInfo<
|
||||
'/admin/privilege/[id]/scope',
|
||||
'/admin/privilege/:id/scope',
|
||||
{ id: ParamValue<true> },
|
||||
{ id: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/admin/role/[id]': RouteRecordInfo<
|
||||
'/admin/role/[id]',
|
||||
'/admin/role/:id',
|
||||
@@ -805,6 +813,7 @@ declare module 'vue-router/auto-routes' {
|
||||
routes:
|
||||
| '/admin/privilege/[id]'
|
||||
| '/admin/privilege/[id]/general'
|
||||
| '/admin/privilege/[id]/scope'
|
||||
views:
|
||||
| 'default'
|
||||
}
|
||||
@@ -814,6 +823,12 @@ declare module 'vue-router/auto-routes' {
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/admin/privilege/[id]/scope.vue': {
|
||||
routes:
|
||||
| '/admin/privilege/[id]/scope'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/admin/role/[id].vue': {
|
||||
routes:
|
||||
| '/admin/role/[id]'
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
- [XO6/Roles] List the groups a role is assigned to in its detail page, assign the role to groups and detach it from them
|
||||
- [XO6/Roles] List the privileges of a role in its detail page, and see the objects each privilege currently applies to in a side panel
|
||||
- [XO6/Privileges] Open a privilege's detail page from the privileges table of a role or its side panel, to see its details
|
||||
- [XO6/Privileges] See the scope of a privilege in its detail page: its scope type, its query and the objects it currently applies to
|
||||
|
||||
### Bug fixes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user