System permissions for app access (#4004)

* Pass relations through schema, instead of individual reads

* Fetch field transforms upfront

* Fix length check

* List if user has app access or not in accountability

* Load permissions up front, merge app access minimal permissions

* Show app access required permissions in permissions overview

* Show system minimal permissions in permissions detail

* Fix app access check in authenticate for jwt use

* Fix minimal permissions for presets

* Remove /permissions/me in favor of root use w/ permissions

* Fix logical nested OR in an AND

* Use root permissions endpoint with filter instead of /me

* Allow filter query on /permissions

* Add system minimal app access permissions into result of /permissions

* Remove stray console log

* Remove stray console.dir

* Set current role as role for minimal permissions

* Fix no-permissions state for user detail

* Add filter items function that allows altering existing result set
This commit is contained in:
Rijk van Zanten
2021-02-11 12:50:56 -05:00
committed by GitHub
parent 8c1402fb88
commit b7d87e581a
55 changed files with 897 additions and 524 deletions

View File

@@ -124,7 +124,7 @@
rounded
icon
:loading="saving"
:disabled="isSavable === false"
:disabled="isSavable === false || saveAllowed === false"
v-tooltip.bottom="saveAllowed ? $t('save') : $t('not_allowed')"
@click="saveAndQuit"
>
@@ -175,7 +175,7 @@
<div class="page-description" v-html="marked($t('page_help_collections_item'))" />
</sidebar-detail>
<revisions-drawer-detail
v-if="isNew === false && _primaryKey"
v-if="isNew === false && _primaryKey && hasRevisionsPermissions"
:collection="collection"
:primary-key="_primaryKey"
ref="revisionsDrawerDetail"
@@ -195,26 +195,19 @@ import { defineComponent, computed, toRefs, ref } from '@vue/composition-api';
import Vue from 'vue';
import CollectionsNavigation from '../components/navigation.vue';
import router from '../../../router';
import router from '@/router';
import CollectionsNotFound from './not-found.vue';
import useCollection from '../../../composables/use-collection';
import RevisionsDrawerDetail from '../../../views/private/components/revisions-drawer-detail';
import CommentsSidebarDetail from '../../../views/private/components/comments-sidebar-detail';
import useItem from '../../../composables/use-item';
import SaveOptions from '../../../views/private/components/save-options';
import i18n from '../../../lang';
import useCollection from '@/composables/use-collection';
import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail';
import CommentsSidebarDetail from '@/views/private/components/comments-sidebar-detail';
import useItem from '@/composables/use-item';
import SaveOptions from '@/views/private/components/save-options';
import i18n from '@/lang';
import marked from 'marked';
import useShortcut from '../../../composables/use-shortcut';
import useShortcut from '@/composables/use-shortcut';
import { NavigationGuard } from 'vue-router';
import { useUserStore, usePermissionsStore } from '../../../stores';
import generateJoi from '../../../utils/generate-joi';
import { cloneDeep } from 'lodash';
import { Field } from '../../../types';
import { usePermissions } from '../../../composables/use-permissions';
type Values = {
[field: string]: any;
};
import { usePermissionsStore } from '@/stores';
import { usePermissions } from '@/composables/use-permissions';
export default defineComponent({
name: 'collections-item',
@@ -241,9 +234,14 @@ export default defineComponent({
},
setup(props) {
const form = ref<HTMLElement>();
const userStore = useUserStore();
const permissionsStore = usePermissionsStore();
const hasRevisionsPermissions = computed(() => {
return !!permissionsStore.state.permissions.find(
(permission) => permission.collection === 'directus_revisions' && permission.action === 'read'
);
});
const { collection, primaryKey } = toRefs(props);
const { breadcrumb } = useBreadcrumb();
@@ -382,6 +380,7 @@ export default defineComponent({
fields,
isSingleton,
_primaryKey,
hasRevisionsPermissions,
};
function useBreadcrumb() {
@@ -488,7 +487,7 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
@import '../../../styles/mixins/breakpoint';
@import '@/styles/mixins/breakpoint';
.action-delete {
--v-button-background-color: var(--danger-25);

View File

@@ -157,7 +157,7 @@
<template #sidebar>
<file-info-sidebar-detail :file="item" />
<revisions-drawer-detail
v-if="isBatch === false && isNew === false"
v-if="isBatch === false && isNew === false && hasRevisionsPermissions"
collection="directus_files"
:primary-key="primaryKey"
ref="revisionsDrawerDetail"
@@ -176,33 +176,26 @@
<script lang="ts">
import { defineComponent, computed, toRefs, ref, watch } from '@vue/composition-api';
import FilesNavigation from '../components/navigation.vue';
import { i18n } from '../../../lang';
import router from '../../../router';
import RevisionsDrawerDetail from '../../../views/private/components/revisions-drawer-detail';
import CommentsSidebarDetail from '../../../views/private/components/comments-sidebar-detail';
import useItem from '../../../composables/use-item';
import SaveOptions from '../../../views/private/components/save-options';
import FilePreview from '../../../views/private/components/file-preview';
import ImageEditor from '../../../views/private/components/image-editor';
import { nanoid } from 'nanoid';
import FileLightbox from '../../../views/private/components/file-lightbox';
import { useFieldsStore } from '../../../stores/';
import { Field } from '../../../types';
import { i18n } from '@/lang';
import router from '@/router';
import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail';
import CommentsSidebarDetail from '@/views/private/components/comments-sidebar-detail';
import useItem from '@/composables/use-item';
import SaveOptions from '@/views/private/components/save-options';
import FilePreview from '@/views/private/components/file-preview';
import ImageEditor from '@/views/private/components/image-editor';
import { Field } from '@/types';
import FileInfoSidebarDetail from '../components/file-info-sidebar-detail.vue';
import useFormFields from '../../../composables/use-form-fields';
import FolderPicker from '../components/folder-picker.vue';
import api, { addTokenToURL } from '../../../api';
import { getRootPath } from '../../../utils/get-root-path';
import api, { addTokenToURL } from '@/api';
import { getRootPath } from '@/utils/get-root-path';
import FilesNotFound from './not-found.vue';
import useShortcut from '../../../composables/use-shortcut';
import useShortcut from '@/composables/use-shortcut';
import ReplaceFile from '../components/replace-file.vue';
import { usePermissions } from '../../../composables/use-permissions';
import { notify } from '../../../utils/notify';
import { unexpectedError } from '../../../utils/unexpected-error';
type Values = {
[field: string]: any;
};
import { usePermissions } from '@/composables/use-permissions';
import { notify } from '@/utils/notify';
import { unexpectedError } from '@/utils/unexpected-error';
import { usePermissionsStore } from '@/stores';
export default defineComponent({
name: 'files-item',
@@ -240,9 +233,16 @@ export default defineComponent({
const form = ref<HTMLElement>();
const { primaryKey } = toRefs(props);
const { breadcrumb } = useBreadcrumb();
const fieldsStore = useFieldsStore();
const replaceFileDialogActive = ref(false);
const permissionsStore = usePermissionsStore();
const hasRevisionsPermissions = computed(() => {
return !!permissionsStore.state.permissions.find(
(permission) => permission.collection === 'directus_revisions' && permission.action === 'read'
);
});
const revisionsDrawerDetail = ref<Vue | null>(null);
const {
@@ -344,6 +344,7 @@ export default defineComponent({
updateAllowed,
fields,
fieldsFiltered,
hasRevisionsPermissions,
};
function useBreadcrumb() {

View File

@@ -33,7 +33,7 @@
import { defineComponent, ref } from '@vue/composition-api';
import api from '@/api';
import router from '@/router';
import { permissions } from './app-required-permissions';
import { appRecommendedPermissions } from './app-recommended-permissions';
import { unexpectedError } from '@/utils/unexpected-error';
export default defineComponent({
@@ -64,7 +64,7 @@ export default defineComponent({
if (appAccess.value === true && adminAccess.value === false) {
await api.post(
'/permissions',
permissions.map((permission) => ({
appRecommendedPermissions.map((permission) => ({
...permission,
role: roleResponse.data.data.id,
}))

View File

@@ -0,0 +1,56 @@
import { Permission } from '@/types';
export const appRecommendedPermissions: Partial<Permission>[] = [
{
collection: 'directus_files',
action: 'create',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_files',
action: 'read',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_files',
action: 'update',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_files',
action: 'delete',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_folders',
action: 'create',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_folders',
action: 'read',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_folders',
action: 'update',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_folders',
action: 'delete',
permissions: {},
},
{
collection: 'directus_users',
action: 'read',
permissions: {},
},
];

View File

@@ -1,129 +0,0 @@
import { Permission } from '@/types';
export const appRequiredPermissions: Partial<Permission>[] = [
{
collection: 'directus_activity',
action: 'read',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_collections',
action: 'read',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_fields',
action: 'read',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_presets',
action: 'create',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_presets',
action: 'read',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_presets',
action: 'update',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_presets',
action: 'delete',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_relations',
action: 'read',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_revisions',
action: 'read',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_users',
action: 'read',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_roles',
action: 'read',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_settings',
action: 'read',
permissions: {},
fields: ['*'],
},
];
export const appRecommendedPermissions: Partial<Permission>[] = [
{
collection: 'directus_files',
action: 'create',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_files',
action: 'read',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_files',
action: 'update',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_files',
action: 'delete',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_folders',
action: 'create',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_folders',
action: 'read',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_folders',
action: 'update',
permissions: {},
fields: ['*'],
},
{
collection: 'directus_folders',
action: 'delete',
permissions: {},
fields: ['*'],
},
];
export const permissions = [...appRequiredPermissions, ...appRecommendedPermissions];

View File

@@ -15,6 +15,7 @@
:role="role"
:permissions="permissions"
:loading="isLoading('create')"
:app-minimal="appMinimal && appMinimal.find((p) => p.action === 'create')"
/>
<permissions-overview-toggle
action="read"
@@ -22,6 +23,7 @@
:role="role"
:permissions="permissions"
:loading="isLoading('read')"
:app-minimal="appMinimal && appMinimal.find((p) => p.action === 'read')"
/>
<permissions-overview-toggle
action="update"
@@ -29,6 +31,7 @@
:role="role"
:permissions="permissions"
:loading="isLoading('update')"
:app-minimal="appMinimal && appMinimal.find((p) => p.action === 'update')"
/>
<permissions-overview-toggle
action="delete"
@@ -36,13 +39,13 @@
:role="role"
:permissions="permissions"
:loading="isLoading('delete')"
:app-minimal="appMinimal && appMinimal.find((p) => p.action === 'delete')"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, inject, toRefs } from '@vue/composition-api';
import api from '@/api';
import { defineComponent, PropType, toRefs } from '@vue/composition-api';
import { Collection, Permission } from '@/types';
import PermissionsOverviewToggle from './permissions-overview-toggle.vue';
import useUpdatePermissions from '../composables/use-update-permissions';
@@ -66,6 +69,10 @@ export default defineComponent({
type: Array as PropType<number[]>,
required: true,
},
appMinimal: {
type: [Boolean, Array] as PropType<false | Partial<Permission>[]>,
default: false,
},
},
setup(props) {
const { collection, role, permissions } = toRefs(props);

View File

@@ -1,17 +1,28 @@
<template>
<div class="permissions-overview-toggle">
<v-menu show-arrow>
<div
class="permissions-overview-toggle"
:class="{ 'has-app-minimal': !!appMinimal }"
v-tooltip="appMinimal && $t('required_for_app_access')"
>
<v-icon v-if="appMinimalLevel === 'full'" name="check" class="all app-minimal" />
<v-menu show-arrow v-else>
<template #activator="{ toggle }">
<div>
<v-progress-circular indeterminate v-if="loading || saving" small />
<v-icon v-else-if="permissionLevel === 'all'" @click="toggle" name="check" class="all" />
<v-icon v-else-if="permissionLevel === 'custom'" @click="toggle" name="rule" class="custom" />
<v-icon
v-else-if="appMinimalLevel === 'partial' || permissionLevel === 'custom'"
@click="toggle"
name="rule"
class="custom"
/>
<v-icon v-else-if="permissionLevel === 'none'" @click="toggle" name="block" class="none" />
</div>
</template>
<v-list>
<v-list-item @click="setFullAccess(action)">
<v-list-item :disabled="permissionLevel === 'all'" @click="setFullAccess(action)">
<v-list-item-icon>
<v-icon name="check" />
</v-list-item-icon>
@@ -20,7 +31,11 @@
</v-list-item-content>
</v-list-item>
<v-list-item @click="setNoAccess(action)">
<v-list-item
v-if="!!appMinimalLevel === false"
:disabled="permissionLevel === 'none'"
@click="setNoAccess(action)"
>
<v-list-item-icon>
<v-icon name="block" />
</v-list-item-icon>
@@ -53,7 +68,6 @@ import { Collection, Permission } from '@/types';
import api from '@/api';
import router from '@/router';
import useUpdatePermissions from '../composables/use-update-permissions';
import { unexpectedError } from '@/utils/unexpected-error';
export default defineComponent({
props: {
@@ -77,6 +91,10 @@ export default defineComponent({
type: Boolean,
default: false,
},
appMinimal: {
type: [Boolean, Object] as PropType<false | Partial<Permission>>,
default: false,
},
},
setup(props) {
const { collection, role, permissions } = toRefs(props);
@@ -104,7 +122,13 @@ export default defineComponent({
const refresh = inject<() => Promise<void>>('refresh-permissions');
return { permissionLevel, saving, setFullAccess, setNoAccess, openPermissions };
const appMinimalLevel = computed(() => {
if (props.appMinimal === false) return null;
if (Object.keys(props.appMinimal).length === 2) return 'full';
return 'partial';
});
return { permissionLevel, saving, setFullAccess, setNoAccess, openPermissions, appMinimalLevel };
async function openPermissions() {
// If this collection isn't "managed" yet, make sure to add it to directus_collections first
@@ -139,6 +163,17 @@ export default defineComponent({
<style lang="scss" scoped>
.permissions-overview-toggle {
position: relative;
&.has-app-minimal::before {
position: absolute;
top: -4px;
left: -4px;
width: calc(100% + 8px);
height: calc(100% + 8px);
background-color: var(--background-highlight);
border-radius: 50%;
content: '';
}
}
.all {
@@ -152,4 +187,8 @@ export default defineComponent({
.none {
--v-icon-color: var(--danger);
}
.app-minimal {
cursor: not-allowed;
}
</style>

View File

@@ -31,29 +31,31 @@
:role="role"
:permissions="permissions.filter((p) => p.collection === collection.collection)"
:refreshing="refreshing"
:app-minimal="appAccess && appMinimalPermissions.filter((p) => p.collection === collection.collection)"
/>
</div>
</transition-expand>
<button v-if="systemVisible" class="reset-toggle" @click="resetActive = true">
{{ $t('reset_system_permissions') }}
</button>
<span class="reset-toggle" v-if="systemVisible && appAccess">
{{ $t('reset_system_permissions_to') }}
<button @click="resetActive = 'minimum'">{{ $t('app_access_minimum') }}</button>
/
<button @click="resetActive = 'recommended'">{{ $t('recommended_defaults') }}</button>
</span>
</div>
<router-view
name="permissionsDetail"
:role-key="role"
:permission-key="permission"
@refresh="refreshPermission"
/>
<router-view name="permissionsDetail" :role-key="role" :permission-key="permission" @refresh="refreshPermission" />
<v-dialog v-model="resetActive" @esc="resetActive = false">
<v-dialog @toggle="resetActive = false" :active="!!resetActive" @esc="resetActive = false">
<v-card>
<v-card-title>{{ $t('reset_system_permissions') }}</v-card-title>
<v-card-text>{{ $t('reset_system_permissions_copy') }}</v-card-text>
<v-card-title>
{{ $t('reset_system_permissions_copy') }}
</v-card-title>
<v-card-actions>
<v-button @click="resetActive = false" secondary>{{ $t('cancel') }}</v-button>
<v-button @click="resetSystemPermissions" :loading="resetting">{{ $t('reset') }}</v-button>
<v-button @click="resetSystemPermissions(resetActive === 'recommended')" :loading="resetting">
{{ $t('reset') }}
</v-button>
</v-card-actions>
</v-card>
</v-dialog>
@@ -67,9 +69,11 @@ import PermissionsOverviewHeader from './permissions-overview-header.vue';
import PermissionsOverviewRow from './permissions-overview-row.vue';
import { Permission } from '@/types';
import api from '@/api';
import { permissions as appRequiredPermissions } from '../../app-required-permissions';
import { appRecommendedPermissions } from '../../app-recommended-permissions';
import { unexpectedError } from '@/utils/unexpected-error';
import appMinimalPermissions from 'directus/dist/database/system-data/app-access-permissions/app-access-permissions.yaml';
export default defineComponent({
components: { PermissionsOverviewHeader, PermissionsOverviewRow },
props: {
@@ -91,15 +95,11 @@ export default defineComponent({
const collectionsStore = useCollectionsStore();
const regularCollections = computed(() =>
collectionsStore.state.collections.filter(
(collection) => collection.collection.startsWith('directus_') === false
)
collectionsStore.state.collections.filter((collection) => collection.collection.startsWith('directus_') === false)
);
const systemCollections = computed(() =>
collectionsStore.state.collections.filter(
(collection) => collection.collection.startsWith('directus_') === true
)
collectionsStore.state.collections.filter((collection) => collection.collection.startsWith('directus_') === true)
);
const systemVisible = ref(false);
@@ -125,6 +125,7 @@ export default defineComponent({
resetSystemPermissions,
resetting,
resetError,
appMinimalPermissions,
};
function usePermissions() {
@@ -177,13 +178,13 @@ export default defineComponent({
}
function useReset() {
const resetActive = ref(false);
const resetActive = ref<string | boolean>(false);
const resetting = ref(false);
const resetError = ref<any>(null);
return { resetActive, resetSystemPermissions, resetting, resetError };
async function resetSystemPermissions() {
async function resetSystemPermissions(useRecommended: boolean) {
resetting.value = true;
const toBeDeleted = permissions.value
@@ -195,10 +196,10 @@ export default defineComponent({
await api.delete(`/permissions/${toBeDeleted.join(',')}`);
}
if (props.role !== null && props.appAccess === true) {
if (props.role !== null && props.appAccess === true && useRecommended === true) {
await api.post(
'/permissions',
appRequiredPermissions.map((permission) => ({
appRecommendedPermissions.map((permission) => ({
...permission,
role: props.role,
}))
@@ -255,10 +256,15 @@ export default defineComponent({
display: block;
margin: 8px auto;
color: var(--foreground-subdued);
transition: color var(--fast) var(--transition);
text-align: center;
&:hover {
color: var(--foreground);
button {
color: var(--primary) !important;
transition: color var(--fast) var(--transition);
}
button:hover {
color: var(--foreground-normal) !important;
}
}
</style>

View File

@@ -7,11 +7,7 @@
</v-button>
</template>
<template #actions>
<v-dialog
v-model="confirmDelete"
v-if="[1, 2].includes(+primaryKey) === false"
@esc="confirmDelete = false"
>
<v-dialog v-model="confirmDelete" v-if="[1, 2].includes(+primaryKey) === false" @esc="confirmDelete = false">
<template #activator="{ on }">
<v-button
rounded
@@ -71,6 +67,7 @@
<v-notice v-if="adminEnabled" type="info">
{{ $t('admins_have_all_permissions') }}
</v-notice>
<permissions-overview v-else :role="primaryKey" :permission="permissionKey" :app-access="appAccess" />
<v-form
@@ -102,10 +99,6 @@ import RoleInfoSidebarDetail from './components/role-info-sidebar-detail.vue';
import PermissionsOverview from './components/permissions-overview.vue';
import UsersInvite from '@/views/private/components/users-invite';
type Values = {
[field: string]: any;
};
export default defineComponent({
name: 'roles-item',
components: { SettingsNavigation, RevisionsDrawerDetail, RoleInfoSidebarDetail, PermissionsOverview, UsersInvite },

View File

@@ -11,6 +11,12 @@
<p class="type-label">{{ $tc('field', 0) }}</p>
<interface-checkboxes v-model="fields" type="json" :choices="fieldsInCollection" />
<div v-if="appMinimal" class="app-minimal">
<v-divider />
<v-notice type="warning">{{ $t('the_following_are_minimum_permissions') }}</v-notice>
<pre class="app-minimal-preview">{{ appMinimal }}</pre>
</div>
</div>
</template>
@@ -30,6 +36,10 @@ export default defineComponent({
type: Object as PropType<Role>,
default: null,
},
appMinimal: {
type: Object as PropType<Partial<Permission>>,
default: undefined,
},
},
setup(props, { emit }) {
const fieldsStore = useFieldsStore();
@@ -92,4 +102,21 @@ export default defineComponent({
.v-notice {
margin-bottom: 36px;
}
.app-minimal {
.v-divider {
margin: 24px 0;
}
.v-notice {
margin-bottom: 24px;
}
.app-minimal-preview {
padding: 16px;
font-family: var(--family-monospace);
background-color: var(--background-subdued);
border-radius: var(--border-radius);
}
}
</style>

View File

@@ -10,6 +10,12 @@
</v-notice>
<interface-code v-model="permissions" language="json" type="json" />
<div v-if="appMinimal" class="app-minimal">
<v-divider />
<v-notice type="warning">{{ $t('the_following_are_minimum_permissions') }}</v-notice>
<pre class="app-minimal-preview">{{ appMinimal }}</pre>
</div>
</div>
</template>
@@ -28,6 +34,10 @@ export default defineComponent({
type: Object as PropType<Role>,
default: null,
},
appMinimal: {
type: Object as PropType<Partial<Permission>>,
default: undefined,
},
},
setup(props, { emit }) {
const _permission = useSync(props, 'permission', emit);
@@ -53,4 +63,21 @@ export default defineComponent({
.v-notice {
margin-bottom: 36px;
}
.app-minimal {
.v-divider {
margin: 24px 0;
}
.v-notice {
margin-bottom: 24px;
}
.app-minimal-preview {
padding: 16px;
font-family: var(--family-monospace);
background-color: var(--background-subdued);
border-radius: var(--border-radius);
}
}
</style>

View File

@@ -11,10 +11,30 @@
</template>
<div class="content" v-if="!loading">
<permissions v-if="currentTab[0] === 'permissions'" :permission.sync="permission" :role="role" />
<fields v-if="currentTab[0] === 'fields'" :permission.sync="permission" :role="role" />
<validation v-if="currentTab[0] === 'validation'" :permission.sync="permission" :role="role" />
<presets v-if="currentTab[0] === 'presets'" :permission.sync="permission" :role="role" />
<permissions
v-if="currentTab[0] === 'permissions'"
:permission.sync="permission"
:role="role"
:app-minimal="appMinimal && appMinimal.permissions"
/>
<fields
v-if="currentTab[0] === 'fields'"
:permission.sync="permission"
:role="role"
:app-minimal="appMinimal && appMinimal.fields"
/>
<validation
v-if="currentTab[0] === 'validation'"
:permission.sync="permission"
:role="role"
:app-minimal="appMinimal && appMinimal.validation"
/>
<presets
v-if="currentTab[0] === 'presets'"
:permission.sync="permission"
:role="role"
:app-minimal="appMinimal && appMinimal.presets"
/>
</div>
<template #actions v-if="!loading">
@@ -24,10 +44,10 @@
</template>
<script lang="ts">
import { defineComponent, ref, reactive, computed, watch } from '@vue/composition-api';
import { defineComponent, ref, computed, watch } from '@vue/composition-api';
import api from '@/api';
import { Permission, Role } from '@/types';
import { useFieldsStore, useCollectionsStore } from '@/stores/';
import { useCollectionsStore } from '@/stores/';
import router from '@/router';
import i18n from '@/lang';
import Actions from './components/actions.vue';
@@ -39,6 +59,8 @@ import Validation from './components/validation.vue';
import Presets from './components/presets.vue';
import { unexpectedError } from '@/utils/unexpected-error';
import appMinimalPermissions from 'directus/dist/database/system-data/app-access-permissions/app-access-permissions.yaml';
export default defineComponent({
components: { Actions, Tabs, Permissions, Fields, Validation, Presets },
props: {
@@ -88,8 +110,7 @@ export default defineComponent({
tabs.push({
text: i18n.t('item_permissions'),
value: 'permissions',
hasValue:
permission.value.permissions !== null && Object.keys(permission.value.permissions).length > 0,
hasValue: permission.value.permissions !== null && Object.keys(permission.value.permissions).length > 0,
});
}
@@ -105,8 +126,7 @@ export default defineComponent({
tabs.push({
text: i18n.t('field_validation'),
value: 'validation',
hasValue:
permission.value.validation !== null && Object.keys(permission.value.validation).length > 0,
hasValue: permission.value.validation !== null && Object.keys(permission.value.validation).length > 0,
});
}
@@ -138,7 +158,15 @@ export default defineComponent({
{ immediate: true }
);
return { permission, role, loading, modalTitle, tabs, currentTab, currentTabInfo };
const appMinimal = computed(() => {
if (!permission.value) return null;
return appMinimalPermissions.find(
(p: Partial<Permission>) =>
p.collection === permission.value!.collection && p.action === permission.value!.action
);
});
return { permission, role, loading, modalTitle, tabs, currentTab, currentTabInfo, appMinimal };
async function load() {
loading.value = true;

View File

@@ -18,10 +18,9 @@
</template>
<script lang="ts">
import { defineComponent, computed, toRefs, ref } from '@vue/composition-api';
import { defineComponent } from '@vue/composition-api';
import SettingsNavigation from '../../components/navigation.vue';
import router from '@/router';
import PermissionsOverview from './item/components/permissions-overview.vue';
export default defineComponent({

View File

@@ -34,6 +34,7 @@ export default defineModule(({ i18n }) => ({
const permission = permissions.find(
(permission) => permission.collection === 'directus_users' && permission.action === 'read'
);
return !!permission;
},
}));

View File

@@ -11,15 +11,15 @@
</template>
<template #actions>
<v-dialog v-model="confirmDelete" @esc="confirmDelete = false">
<v-dialog v-model="confirmDelete" @esc="confirmDelete = false" :disabled="deleteAllowed === false">
<template #activator="{ on }">
<v-button
rounded
icon
class="action-delete"
:disabled="item === null"
v-tooltip.bottom="deleteAllowed ? $t('delete') : $t('not_allowed')"
:disabled="item === null || deleteAllowed !== true"
@click="on"
v-tooltip.bottom="$t('delete')"
>
<v-icon name="delete" outline />
</v-button>
@@ -77,9 +77,9 @@
rounded
icon
:loading="saving"
:disabled="hasEdits === false"
:disabled="hasEdits === false || saveAllowed === false"
v-tooltip.bottom="saveAllowed ? $t('save') : $t('not_allowed')"
@click="saveAndQuit"
v-tooltip.bottom="$t('save')"
>
<v-icon name="check" />
@@ -125,6 +125,7 @@
<v-form
ref="form"
:disabled="isNew ? createAllowed === false : updateAllowed === false"
:fields="formFields"
:loading="loading"
:initial-values="item"
@@ -150,7 +151,7 @@
<template #sidebar>
<user-info-sidebar-detail :is-new="isNew" :user="item" />
<revisions-drawer-detail
v-if="isBatch === false && isNew === false"
v-if="isBatch === false && isNew === false && hasRevisionsPermissions"
collection="directus_users"
:primary-key="primaryKey"
ref="revisionsDrawerDetail"
@@ -168,28 +169,25 @@
import { defineComponent, computed, toRefs, ref, watch } from '@vue/composition-api';
import UsersNavigation from '../components/navigation.vue';
import { i18n, setLanguage } from '../../../lang';
import router from '../../../router';
import RevisionsDrawerDetail from '../../../views/private/components/revisions-drawer-detail';
import CommentsSidebarDetail from '../../../views/private/components/comments-sidebar-detail';
import useItem from '../../../composables/use-item';
import SaveOptions from '../../../views/private/components/save-options';
import api from '../../../api';
import { useFieldsStore, useCollectionsStore, useUserStore } from '../../../stores/';
import useFormFields from '../../../composables/use-form-fields';
import { Field } from '../../../types';
import { i18n, setLanguage } from '@/lang';
import router from '@/router';
import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail';
import CommentsSidebarDetail from '@/views/private/components/comments-sidebar-detail';
import useItem from '@/composables/use-item';
import SaveOptions from '@/views/private/components/save-options';
import api from '@/api';
import { useFieldsStore, useCollectionsStore, useUserStore } from '@/stores/';
import useFormFields from '@/composables/use-form-fields';
import { Field } from '@/types';
import UserInfoSidebarDetail from '../components/user-info-sidebar-detail.vue';
import { getRootPath } from '../../../utils/get-root-path';
import useShortcut from '../../../composables/use-shortcut';
import useCollection from '../../../composables/use-collection';
import { userName } from '../../../utils/user-name';
import { usePermissions } from '../../../composables/use-permissions';
import { unexpectedError } from '../../../utils/unexpected-error';
import { addTokenToURL } from '../../../api';
type Values = {
[field: string]: any;
};
import { getRootPath } from '@/utils/get-root-path';
import useShortcut from '@/composables/use-shortcut';
import useCollection from '@/composables/use-collection';
import { userName } from '@/utils/user-name';
import { usePermissions } from '@/composables/use-permissions';
import { unexpectedError } from '@/utils/unexpected-error';
import { addTokenToURL } from '@/api';
import { usePermissionsStore } from '@/stores';
export default defineComponent({
name: 'users-item',
@@ -221,6 +219,13 @@ export default defineComponent({
const fieldsStore = useFieldsStore();
const collectionsStore = useCollectionsStore();
const userStore = useUserStore();
const permissionsStore = usePermissionsStore();
const hasRevisionsPermissions = computed(() => {
return !!permissionsStore.state.permissions.find(
(permission) => permission.collection === 'directus_revisions' && permission.action === 'read'
);
});
const { primaryKey } = toRefs(props);
const { breadcrumb } = useBreadcrumb();
@@ -346,6 +351,7 @@ export default defineComponent({
archiveTooltip,
form,
userName,
hasRevisionsPermissions,
};
function useBreadcrumb() {