script[setup]: rest of settings (#18447)

This commit is contained in:
Rijk van Zanten
2023-05-03 11:50:20 -04:00
committed by GitHub
parent ef272d18d6
commit 42e69bbb27
20 changed files with 974 additions and 1289 deletions

View File

@@ -25,71 +25,65 @@
</v-list>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
<script setup lang="ts">
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
export default defineComponent({
setup() {
const version = __DIRECTUS_VERSION__;
const version = __DIRECTUS_VERSION__;
const { t } = useI18n();
const { t } = useI18n();
const navItems = [
{
icon: 'public',
name: t('settings_project'),
to: `/settings/project`,
},
{
icon: 'list_alt',
name: t('settings_data_model'),
to: `/settings/data-model`,
},
{
icon: 'admin_panel_settings',
name: t('settings_permissions'),
to: `/settings/roles`,
},
{
icon: 'bookmark',
name: t('settings_presets'),
to: `/settings/presets`,
},
{
icon: 'translate',
name: t('settings_translation_strings'),
to: `/settings/translation-strings`,
},
{
icon: 'anchor',
name: t('settings_webhooks'),
to: `/settings/webhooks`,
},
{
icon: 'bolt',
name: t('settings_flows'),
to: `/settings/flows`,
},
];
const externalItems = computed(() => {
return [
{
icon: 'bug_report',
name: t('report_bug'),
href: 'https://github.com/directus/directus/issues/new?template=bug_report.yml',
},
{
icon: 'new_releases',
name: t('request_feature'),
href: 'https://github.com/directus/directus/discussions/new?category=feature-requests',
},
];
});
return { version, navItems, externalItems };
const navItems = [
{
icon: 'public',
name: t('settings_project'),
to: `/settings/project`,
},
{
icon: 'list_alt',
name: t('settings_data_model'),
to: `/settings/data-model`,
},
{
icon: 'admin_panel_settings',
name: t('settings_permissions'),
to: `/settings/roles`,
},
{
icon: 'bookmark',
name: t('settings_presets'),
to: `/settings/presets`,
},
{
icon: 'translate',
name: t('settings_translation_strings'),
to: `/settings/translation-strings`,
},
{
icon: 'anchor',
name: t('settings_webhooks'),
to: `/settings/webhooks`,
},
{
icon: 'bolt',
name: t('settings_flows'),
to: `/settings/flows`,
},
];
const externalItems = computed(() => {
return [
{
icon: 'bug_report',
name: t('report_bug'),
href: 'https://github.com/directus/directus/issues/new?template=bug_report.yml',
},
{
icon: 'new_releases',
name: t('request_feature'),
href: 'https://github.com/directus/directus/discussions/new?category=feature-requests',
},
];
});
</script>

View File

@@ -12,18 +12,11 @@
</private-view>
</template>
<script lang="ts">
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent } from 'vue';
import SettingsNavigation from '../components/navigation.vue';
export default defineComponent({
components: { SettingsNavigation },
setup() {
const { t } = useI18n();
return { t };
},
});
const { t } = useI18n();
</script>
<style lang="scss" scoped>

View File

@@ -37,20 +37,13 @@
</sidebar-detail>
</template>
<script lang="ts">
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent } from 'vue';
import { useProjectInfo } from '../../../composables/use-project-info';
export default defineComponent({
setup() {
const { t } = useI18n();
const { t } = useI18n();
const { parsedInfo } = useProjectInfo();
return { t, parsedInfo };
},
});
const { parsedInfo } = useProjectInfo();
</script>
<style lang="scss" scoped>

View File

@@ -40,76 +40,58 @@
</private-view>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, ref, computed } from 'vue';
import SettingsNavigation from '../../components/navigation.vue';
import { useCollection } from '@directus/composables';
import { useSettingsStore } from '@/stores/settings';
import { useServerStore } from '@/stores/server';
import ProjectInfoSidebarDetail from './components/project-info-sidebar-detail.vue';
import { clone } from 'lodash';
import { useShortcut } from '@/composables/use-shortcut';
<script setup lang="ts">
import { useEditsGuard } from '@/composables/use-edits-guard';
import { useShortcut } from '@/composables/use-shortcut';
import { useServerStore } from '@/stores/server';
import { useSettingsStore } from '@/stores/settings';
import { useCollection } from '@directus/composables';
import { clone } from 'lodash';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import SettingsNavigation from '../../components/navigation.vue';
import ProjectInfoSidebarDetail from './components/project-info-sidebar-detail.vue';
export default defineComponent({
components: { SettingsNavigation, ProjectInfoSidebarDetail },
setup() {
const { t } = useI18n();
const { t } = useI18n();
const router = useRouter();
const router = useRouter();
const settingsStore = useSettingsStore();
const serverStore = useServerStore();
const settingsStore = useSettingsStore();
const serverStore = useServerStore();
const { fields } = useCollection('directus_settings');
const { fields } = useCollection('directus_settings');
const initialValues = ref(clone(settingsStore.settings));
const initialValues = ref(clone(settingsStore.settings));
const edits = ref<{ [key: string]: any } | null>(null);
const edits = ref<{ [key: string]: any } | null>(null);
const hasEdits = computed(() => edits.value !== null && Object.keys(edits.value).length > 0);
const hasEdits = computed(() => edits.value !== null && Object.keys(edits.value).length > 0);
const saving = ref(false);
const saving = ref(false);
useShortcut('meta+s', () => {
if (hasEdits.value) save();
});
const { confirmLeave, leaveTo } = useEditsGuard(hasEdits);
return {
t,
fields,
initialValues,
edits,
hasEdits,
saving,
confirmLeave,
leaveTo,
save,
discardAndLeave,
};
async function save() {
if (edits.value === null) return;
saving.value = true;
await settingsStore.updateSettings(edits.value);
await serverStore.hydrate({ isLanguageUpdated: 'default_language' in edits.value });
edits.value = null;
saving.value = false;
initialValues.value = clone(settingsStore.settings);
}
function discardAndLeave() {
if (!leaveTo.value) return;
edits.value = {};
confirmLeave.value = false;
router.push(leaveTo.value);
}
},
useShortcut('meta+s', () => {
if (hasEdits.value) save();
});
const { confirmLeave, leaveTo } = useEditsGuard(hasEdits);
async function save() {
if (edits.value === null) return;
saving.value = true;
await settingsStore.updateSettings(edits.value);
await serverStore.hydrate({ isLanguageUpdated: 'default_language' in edits.value });
edits.value = null;
saving.value = false;
initialValues.value = clone(settingsStore.settings);
}
function discardAndLeave() {
if (!leaveTo.value) return;
edits.value = {};
confirmLeave.value = false;
router.push(leaveTo.value);
}
</script>
<style lang="scss" scoped>

View File

@@ -29,66 +29,60 @@
</v-dialog>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, ref } from 'vue';
<script setup lang="ts">
import api from '@/api';
import { useDialogRoute } from '@/composables/use-dialog-route';
import { unexpectedError } from '@/utils/unexpected-error';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { appRecommendedPermissions } from './app-permissions';
import { unexpectedError } from '@/utils/unexpected-error';
import { useDialogRoute } from '@/composables/use-dialog-route';
export default defineComponent({
setup() {
const { t } = useI18n();
const { t } = useI18n();
const router = useRouter();
const router = useRouter();
const isOpen = useDialogRoute();
const isOpen = useDialogRoute();
const roleName = ref<string | null>(null);
const appAccess = ref(true);
const adminAccess = ref(false);
const roleName = ref<string | null>(null);
const appAccess = ref(true);
const adminAccess = ref(false);
const { saving, save } = useSave();
const { saving, save } = useSave();
return { t, router, isOpen, roleName, saving, save, appAccess, adminAccess };
function useSave() {
const saving = ref(false);
function useSave() {
const saving = ref(false);
return { saving, save };
return { saving, save };
async function save() {
saving.value = true;
async function save() {
saving.value = true;
try {
const roleResponse = await api.post('/roles', {
name: roleName.value,
admin_access: adminAccess.value,
app_access: appAccess.value,
});
try {
const roleResponse = await api.post('/roles', {
name: roleName.value,
admin_access: adminAccess.value,
app_access: appAccess.value,
});
if (appAccess.value === true && adminAccess.value === false) {
await api.post(
'/permissions',
appRecommendedPermissions.map((permission) => ({
...permission,
role: roleResponse.data.data.id,
}))
);
}
router.push(`/settings/roles/${roleResponse.data.data.id}`);
} catch (err: any) {
unexpectedError(err);
} finally {
saving.value = false;
}
if (appAccess.value === true && adminAccess.value === false) {
await api.post(
'/permissions',
appRecommendedPermissions.map((permission) => ({
...permission,
role: roleResponse.data.data.id,
}))
);
}
router.push(`/settings/roles/${roleResponse.data.data.id}`);
} catch (err: any) {
unexpectedError(err);
} finally {
saving.value = false;
}
},
});
}
}
</script>
<style lang="scss" scoped>

View File

@@ -55,136 +55,126 @@
</private-view>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, computed, ref } from 'vue';
import SettingsNavigation from '../../components/navigation.vue';
<script setup lang="ts">
import api from '@/api';
import { Header as TableHeader } from '@/components/v-table/types';
import { useRouter } from 'vue-router';
import { unexpectedError } from '@/utils/unexpected-error';
import { translate } from '@/utils/translate-object-values';
import { unexpectedError } from '@/utils/unexpected-error';
import { Role } from '@directus/types';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import SettingsNavigation from '../../components/navigation.vue';
type RoleItem = Partial<Role> & {
count?: number;
};
export default defineComponent({
name: 'RolesCollection',
components: { SettingsNavigation },
props: {},
setup() {
const { t } = useI18n();
const { t } = useI18n();
const router = useRouter();
const router = useRouter();
const roles = ref<RoleItem[]>([]);
const loading = ref(false);
const roles = ref<RoleItem[]>([]);
const loading = ref(false);
const lastAdminRoleId = computed(() => {
const adminRoles = roles.value.filter((role) => role.admin_access === true);
return adminRoles.length === 1 ? adminRoles[0].id : null;
});
const tableHeaders = ref<TableHeader[]>([
{
text: '',
value: 'icon',
sortable: false,
width: 42,
align: 'left',
description: null,
},
{
text: t('name'),
value: 'name',
sortable: false,
width: 200,
align: 'left',
description: null,
},
{
text: t('users'),
value: 'count',
sortable: false,
width: 140,
align: 'left',
description: null,
},
{
text: t('description'),
value: 'description',
sortable: false,
width: 470,
align: 'left',
description: null,
},
]);
fetchRoles();
const addNewLink = computed(() => {
return `/settings/roles/+`;
});
return { t, loading, roles, tableHeaders, addNewLink, navigateToRole };
async function fetchRoles() {
loading.value = true;
try {
const response = await api.get(`/roles`, {
params: {
limit: -1,
fields: ['id', 'name', 'description', 'icon', 'admin_access', 'users'],
deep: {
users: {
_aggregate: { count: 'id' },
_groupBy: ['role'],
_sort: 'role',
_limit: -1,
},
},
sort: 'name',
},
});
roles.value = [
{
public: true,
name: t('public_label'),
icon: 'public',
description: t('public_description'),
id: 'public',
},
...response.data.data.map((role: any) => {
return {
...translate(role),
count: role.users[0]?.count.id || 0,
};
}),
];
} catch (err: any) {
unexpectedError(err);
} finally {
loading.value = false;
}
}
function navigateToRole({ item }: { item: Role }) {
if (item.id !== 'public' && lastAdminRoleId.value) {
router.push({
name: 'settings-roles-item',
params: { primaryKey: item.id, lastAdminRoleId: lastAdminRoleId.value },
});
} else {
router.push(`/settings/roles/${item.id}`);
}
}
},
const lastAdminRoleId = computed(() => {
const adminRoles = roles.value.filter((role) => role.admin_access === true);
return adminRoles.length === 1 ? adminRoles[0].id : null;
});
const tableHeaders = ref<TableHeader[]>([
{
text: '',
value: 'icon',
sortable: false,
width: 42,
align: 'left',
description: null,
},
{
text: t('name'),
value: 'name',
sortable: false,
width: 200,
align: 'left',
description: null,
},
{
text: t('users'),
value: 'count',
sortable: false,
width: 140,
align: 'left',
description: null,
},
{
text: t('description'),
value: 'description',
sortable: false,
width: 470,
align: 'left',
description: null,
},
]);
fetchRoles();
const addNewLink = computed(() => {
return `/settings/roles/+`;
});
async function fetchRoles() {
loading.value = true;
try {
const response = await api.get(`/roles`, {
params: {
limit: -1,
fields: ['id', 'name', 'description', 'icon', 'admin_access', 'users'],
deep: {
users: {
_aggregate: { count: 'id' },
_groupBy: ['role'],
_sort: 'role',
_limit: -1,
},
},
sort: 'name',
},
});
roles.value = [
{
public: true,
name: t('public_label'),
icon: 'public',
description: t('public_description'),
id: 'public',
},
...response.data.data.map((role: any) => {
return {
...translate(role),
count: role.users[0]?.count.id || 0,
};
}),
];
} catch (err: any) {
unexpectedError(err);
} finally {
loading.value = false;
}
}
function navigateToRole({ item }: { item: Role }) {
if (item.id !== 'public' && lastAdminRoleId.value) {
router.push({
name: 'settings-roles-item',
params: { primaryKey: item.id, lastAdminRoleId: lastAdminRoleId.value },
});
} else {
router.push(`/settings/roles/${item.id}`);
}
}
</script>
<style lang="scss" scoped>

View File

@@ -52,52 +52,36 @@
</div>
</template>
<script lang="ts">
<script setup lang="ts">
import { Collection, Permission } from '@directus/types';
import { toRefs } from 'vue';
import { useI18n } from 'vue-i18n';
import { defineComponent, PropType, toRefs } from 'vue';
import { Permission, Collection } from '@directus/types';
import PermissionsOverviewToggle from './permissions-overview-toggle.vue';
import useUpdatePermissions from '../composables/use-update-permissions';
import PermissionsOverviewToggle from './permissions-overview-toggle.vue';
export default defineComponent({
components: { PermissionsOverviewToggle },
props: {
role: {
type: String,
default: null,
},
collection: {
type: Object as PropType<Collection>,
required: true,
},
permissions: {
type: Array as PropType<Permission[]>,
required: true,
},
refreshing: {
type: Array as PropType<number[]>,
required: true,
},
appMinimal: {
type: [Boolean, Array] as PropType<false | Partial<Permission>[]>,
default: false,
},
},
setup(props) {
const { t } = useI18n();
const props = withDefaults(
defineProps<{
collection: Collection;
permissions: Permission[];
refreshing: number[];
role?: string;
appMinimal?: false | Partial<Permission>[];
}>(),
{
appMinimal: false,
}
);
const { collection, role, permissions } = toRefs(props);
const { setFullAccessAll, setNoAccessAll, getPermission } = useUpdatePermissions(collection, permissions, role);
const { t } = useI18n();
return { t, getPermission, isLoading, setFullAccessAll, setNoAccessAll };
const { collection, role, permissions } = toRefs(props);
const { setFullAccessAll, setNoAccessAll, getPermission } = useUpdatePermissions(collection, permissions, role);
function isLoading(action: string) {
const permission = getPermission(action);
if (!permission) return false;
return props.refreshing.includes(permission.id);
}
},
});
function isLoading(action: string) {
const permission = getPermission(action);
if (!permission) return false;
return props.refreshing.includes(permission.id);
}
</script>
<style lang="scss" scoped>

View File

@@ -63,105 +63,88 @@
</div>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, PropType, computed, inject, ref, toRefs } from 'vue';
import { Permission, Collection } from '@directus/types';
<script setup lang="ts">
import api from '@/api';
import { Collection, Permission } from '@directus/types';
import { computed, inject, ref, toRefs } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import useUpdatePermissions from '../composables/use-update-permissions';
export default defineComponent({
props: {
collection: {
type: Object as PropType<Collection>,
required: true,
},
role: {
type: String,
default: null,
},
action: {
type: String,
required: true,
},
permissions: {
type: Array as PropType<Permission[]>,
default: null,
},
loading: {
type: Boolean,
default: false,
},
appMinimal: {
type: [Boolean, Object] as PropType<false | Partial<Permission>>,
default: false,
},
},
setup(props) {
const { t } = useI18n();
const props = withDefaults(
defineProps<{
collection: Collection;
action: string;
role?: string;
permissions?: Permission[];
loading?: boolean;
appMinimal?: false | Partial<Permission>;
}>(),
{
loading: false,
appMinimal: false,
}
);
const router = useRouter();
const { t } = useI18n();
const { collection, role, permissions } = toRefs(props);
const { setFullAccess, setNoAccess, getPermission } = useUpdatePermissions(collection, permissions, role);
const router = useRouter();
const permission = computed(() => getPermission(props.action));
const { collection, role, permissions } = toRefs(props);
const { setFullAccess, setNoAccess, getPermission } = useUpdatePermissions(collection, permissions, role);
const permissionLevel = computed<'all' | 'none' | 'custom'>(() => {
if (permission.value === undefined) return 'none';
const permission = computed(() => getPermission(props.action));
if (
permission.value.fields?.includes('*') &&
Object.keys(permission.value.permissions || {}).length === 0 &&
Object.keys(permission.value.validation || {}).length === 0
) {
return 'all';
}
const permissionLevel = computed<'all' | 'none' | 'custom'>(() => {
if (permission.value === undefined) return 'none';
return 'custom';
});
if (
permission.value.fields?.includes('*') &&
Object.keys(permission.value.permissions || {}).length === 0 &&
Object.keys(permission.value.validation || {}).length === 0
) {
return 'all';
}
const saving = ref(false);
const refresh = inject<() => Promise<void>>('refresh-permissions');
const appMinimalLevel = computed(() => {
if (props.appMinimal === false) return null;
if (Object.keys(props.appMinimal).length === 2) return 'full';
return 'partial';
});
return { t, 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
// before trying to associate any permissions with it
if (props.collection.meta === null) {
await api.patch(`/collections/${props.collection.collection}`, {
meta: {},
});
}
if (permission.value) {
router.push(`/settings/roles/${props.role || 'public'}/${permission.value.id}`);
} else {
saving.value = true;
const permResponse = await api.post('/permissions', {
role: props.role,
collection: props.collection.collection,
action: props.action,
});
await refresh?.();
saving.value = false;
router.push(`/settings/roles/${props.role || 'public'}/${permResponse.data.data.id}`);
}
}
},
return 'custom';
});
const saving = ref(false);
const refresh = inject<() => Promise<void>>('refresh-permissions');
const appMinimalLevel = computed(() => {
if (props.appMinimal === false) return null;
if (Object.keys(props.appMinimal).length === 2) return 'full';
return 'partial';
});
async function openPermissions() {
// If this collection isn't "managed" yet, make sure to add it to directus_collections first
// before trying to associate any permissions with it
if (props.collection.meta === null) {
await api.patch(`/collections/${props.collection.collection}`, {
meta: {},
});
}
if (permission.value) {
router.push(`/settings/roles/${props.role || 'public'}/${permission.value.id}`);
} else {
saving.value = true;
const permResponse = await api.post('/permissions', {
role: props.role,
collection: props.collection.collection,
action: props.action,
});
await refresh?.();
saving.value = false;
router.push(`/settings/roles/${props.role || 'public'}/${permResponse.data.data.id}`);
}
}
</script>
<style lang="scss" scoped>

View File

@@ -62,168 +62,143 @@
</div>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, computed, ref, provide, watch } from 'vue';
<script setup lang="ts">
import api from '@/api';
import { useCollectionsStore } from '@/stores/collections';
import { unexpectedError } from '@/utils/unexpected-error';
import { Permission } from '@directus/types';
import { orderBy } from 'lodash';
import { computed, provide, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { appMinimalPermissions, appRecommendedPermissions } from '../../app-permissions';
import PermissionsOverviewHeader from './permissions-overview-header.vue';
import PermissionsOverviewRow from './permissions-overview-row.vue';
import { Permission } from '@directus/types';
import api from '@/api';
import { appRecommendedPermissions, appMinimalPermissions } from '../../app-permissions';
import { unexpectedError } from '@/utils/unexpected-error';
import { orderBy } from 'lodash';
export default defineComponent({
components: { PermissionsOverviewHeader, PermissionsOverviewRow },
props: {
role: {
type: String,
default: null,
},
permission: {
// the permission row primary key in case we're on the permission detail modal view
type: String,
default: null,
},
appAccess: {
type: Boolean,
default: false,
},
},
setup(props) {
const { t } = useI18n();
const props = withDefaults(
defineProps<{
role?: string;
// the permission row primary key in case we're on the permission detail modal view
permission?: string;
appAccess?: boolean;
}>(),
{
appAccess: false,
}
);
const collectionsStore = useCollectionsStore();
const { t } = useI18n();
const regularCollections = computed(() => collectionsStore.databaseCollections);
const collectionsStore = useCollectionsStore();
const systemCollections = computed(() =>
orderBy(
collectionsStore.collections.filter((collection) => collection.collection.startsWith('directus_') === true),
'name'
)
);
const regularCollections = computed(() => collectionsStore.databaseCollections);
const systemVisible = ref(false);
const systemCollections = computed(() =>
orderBy(
collectionsStore.collections.filter((collection) => collection.collection.startsWith('directus_') === true),
'name'
)
);
const { permissions, loading, fetchPermissions, refreshPermission, refreshing } = usePermissions();
const systemVisible = ref(false);
const { resetActive, resetSystemPermissions, resetting, resetError } = useReset();
const { permissions, fetchPermissions, refreshing } = usePermissions();
fetchPermissions();
const { resetActive, resetSystemPermissions, resetting } = useReset();
watch(() => props.permission, fetchPermissions, { immediate: true });
fetchPermissions();
provide('refresh-permissions', fetchPermissions);
watch(() => props.permission, fetchPermissions, { immediate: true });
return {
t,
systemVisible,
regularCollections,
systemCollections,
permissions,
loading,
fetchPermissions,
refreshPermission,
refreshing,
resetActive,
resetSystemPermissions,
resetting,
resetError,
appMinimalPermissions,
};
provide('refresh-permissions', fetchPermissions);
function usePermissions() {
const permissions = ref<Permission[]>([]);
const loading = ref(false);
const refreshing = ref<number[]>([]);
function usePermissions() {
const permissions = ref<Permission[]>([]);
const loading = ref(false);
const refreshing = ref<number[]>([]);
return { permissions, loading, fetchPermissions, refreshPermission, refreshing };
return { permissions, loading, fetchPermissions, refreshPermission, refreshing };
async function fetchPermissions() {
loading.value = true;
async function fetchPermissions() {
loading.value = true;
try {
const params: any = { filter: { role: {} }, limit: -1 };
try {
const params: any = { filter: { role: {} }, limit: -1 };
if (props.role === null) {
params.filter.role = { _null: true };
} else {
params.filter.role = { _eq: props.role };
}
const response = await api.get('/permissions', { params });
permissions.value = response.data.data;
} catch (err: any) {
unexpectedError(err);
} finally {
loading.value = false;
}
if (props.role === null) {
params.filter.role = { _null: true };
} else {
params.filter.role = { _eq: props.role };
}
async function refreshPermission(id: number) {
if (refreshing.value.includes(id) === false) {
refreshing.value.push(id);
}
const response = await api.get('/permissions', { params });
try {
const response = await api.get(`/permissions/${id}`);
permissions.value = response.data.data;
} catch (err: any) {
unexpectedError(err);
} finally {
loading.value = false;
}
}
permissions.value = permissions.value.map((permission) => {
if (permission.id === id) return response.data.data;
return permission;
});
} catch (err: any) {
unexpectedError(err);
} finally {
refreshing.value = refreshing.value.filter((inProgressID) => inProgressID !== id);
}
}
async function refreshPermission(id: number) {
if (refreshing.value.includes(id) === false) {
refreshing.value.push(id);
}
function useReset() {
const resetActive = ref<string | boolean>(false);
const resetting = ref(false);
const resetError = ref<any>(null);
try {
const response = await api.get(`/permissions/${id}`);
return { resetActive, resetSystemPermissions, resetting, resetError };
async function resetSystemPermissions(useRecommended: boolean) {
resetting.value = true;
const toBeDeleted = permissions.value
.filter((permission) => permission.collection.startsWith('directus_'))
.map((permission) => permission.id);
try {
if (toBeDeleted.length > 0) {
await api.delete(`/permissions`, { data: toBeDeleted });
}
if (props.role !== null && props.appAccess === true && useRecommended === true) {
await api.post(
'/permissions',
appRecommendedPermissions.map((permission) => ({
...permission,
role: props.role,
}))
);
}
await fetchPermissions();
resetActive.value = false;
} catch (err: any) {
resetError.value = err;
} finally {
resetting.value = false;
}
}
permissions.value = permissions.value.map((permission) => {
if (permission.id === id) return response.data.data;
return permission;
});
} catch (err: any) {
unexpectedError(err);
} finally {
refreshing.value = refreshing.value.filter((inProgressID) => inProgressID !== id);
}
},
});
}
}
function useReset() {
const resetActive = ref<string | boolean>(false);
const resetting = ref(false);
const resetError = ref<any>(null);
return { resetActive, resetSystemPermissions, resetting, resetError };
async function resetSystemPermissions(useRecommended: boolean) {
resetting.value = true;
const toBeDeleted = permissions.value
.filter((permission) => permission.collection.startsWith('directus_'))
.map((permission) => permission.id);
try {
if (toBeDeleted.length > 0) {
await api.delete(`/permissions`, { data: toBeDeleted });
}
if (props.role !== null && props.appAccess === true && useRecommended === true) {
await api.post(
'/permissions',
appRecommendedPermissions.map((permission) => ({
...permission,
role: props.role,
}))
);
}
await fetchPermissions();
resetActive.value = false;
} catch (err: any) {
resetError.value = err;
} finally {
resetting.value = false;
}
}
}
</script>
<style lang="scss" scoped>

View File

@@ -105,10 +105,7 @@
</private-view>
</template>
<script lang="ts">
import { computed, defineComponent, ref, toRefs } from 'vue';
import { useI18n } from 'vue-i18n';
<script setup lang="ts">
import { useEditsGuard } from '@/composables/use-edits-guard';
import { useItem } from '@/composables/use-item';
import { useShortcut } from '@/composables/use-shortcut';
@@ -117,146 +114,111 @@ import { useServerStore } from '@/stores/server';
import { useUserStore } from '@/stores/user';
import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail.vue';
import UsersInvite from '@/views/private/components/users-invite.vue';
import { computed, ref, toRefs } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import SettingsNavigation from '../../../components/navigation.vue';
import PermissionsOverview from './components/permissions-overview.vue';
import RoleInfoSidebarDetail from './components/role-info-sidebar-detail.vue';
export default defineComponent({
name: 'RolesItem',
components: { SettingsNavigation, RevisionsDrawerDetail, RoleInfoSidebarDetail, PermissionsOverview, UsersInvite },
props: {
primaryKey: {
type: String,
required: true,
},
permissionKey: {
type: String,
default: null,
},
lastAdminRoleId: {
type: String,
default: null,
},
},
setup(props) {
const { t } = useI18n();
const props = defineProps<{
primaryKey: string;
permissionKey?: string;
lastAdminRoleId?: string;
}>();
const router = useRouter();
const { t } = useI18n();
const userStore = useUserStore();
const permissionsStore = usePermissionsStore();
const serverStore = useServerStore();
const userInviteModalActive = ref(false);
const { primaryKey } = toRefs(props);
const router = useRouter();
const revisionsDrawerDetailRef = ref<InstanceType<typeof RevisionsDrawerDetail> | null>(null);
const userStore = useUserStore();
const permissionsStore = usePermissionsStore();
const serverStore = useServerStore();
const userInviteModalActive = ref(false);
const { primaryKey } = toRefs(props);
const { edits, hasEdits, item, saving, loading, error, save, remove, deleting, isBatch } = useItem(
ref('directus_roles'),
primaryKey,
{ deep: { users: { _limit: 0 } } }
);
const revisionsDrawerDetailRef = ref<InstanceType<typeof RevisionsDrawerDetail> | null>(null);
const confirmDelete = ref(false);
const { edits, hasEdits, item, saving, loading, save, remove, deleting, isBatch } = useItem(
ref('directus_roles'),
primaryKey,
{ deep: { users: { _limit: 0 } } }
);
const adminEnabled = computed(() => {
const values = {
...item.value,
...edits.value,
} as Record<string, any>;
const confirmDelete = ref(false);
return !!values.admin_access;
});
const adminEnabled = computed(() => {
const values = {
...item.value,
...edits.value,
} as Record<string, any>;
const appAccess = computed(() => {
const values = {
...item.value,
...edits.value,
} as Record<string, any>;
return !!values.app_access;
});
useShortcut('meta+s', () => {
if (hasEdits.value) saveAndStay();
});
const { confirmLeave, leaveTo } = useEditsGuard(hasEdits);
const canInviteUsers = computed(() => {
if (serverStore.auth.disableDefault === true) return false;
const isAdmin = !!userStore.currentUser?.role?.admin_access;
if (isAdmin) return true;
const usersCreatePermission = permissionsStore.permissions.find(
(permission) => permission.collection === 'directus_users' && permission.action === 'create'
);
const rolesReadPermission = permissionsStore.permissions.find(
(permission) => permission.collection === 'directus_roles' && permission.action === 'read'
);
return !!usersCreatePermission && !!rolesReadPermission;
});
return {
t,
item,
loading,
error,
edits,
hasEdits,
saving,
saveAndQuit,
deleteAndQuit,
confirmDelete,
deleting,
isBatch,
adminEnabled,
userInviteModalActive,
appAccess,
confirmLeave,
leaveTo,
discardAndLeave,
canInviteUsers,
revisionsDrawerDetailRef,
};
/**
* @NOTE
* The userStore contains the information about the role of the current user. We want to
* update the userstore to make sure the role information is accurate with the latest changes
* in case we're changing the current user's role
*/
async function saveAndStay() {
await save();
await userStore.hydrate();
revisionsDrawerDetailRef.value?.refresh?.();
}
async function saveAndQuit() {
await save();
await userStore.hydrate();
router.push(`/settings/roles`);
}
async function deleteAndQuit() {
await remove();
edits.value = {};
router.replace(`/settings/roles`);
}
function discardAndLeave() {
if (!leaveTo.value) return;
edits.value = {};
confirmLeave.value = false;
router.push(leaveTo.value);
}
},
return !!values.admin_access;
});
const appAccess = computed(() => {
const values = {
...item.value,
...edits.value,
} as Record<string, any>;
return !!values.app_access;
});
useShortcut('meta+s', () => {
if (hasEdits.value) saveAndStay();
});
const { confirmLeave, leaveTo } = useEditsGuard(hasEdits);
const canInviteUsers = computed(() => {
if (serverStore.auth.disableDefault === true) return false;
const isAdmin = !!userStore.currentUser?.role?.admin_access;
if (isAdmin) return true;
const usersCreatePermission = permissionsStore.permissions.find(
(permission) => permission.collection === 'directus_users' && permission.action === 'create'
);
const rolesReadPermission = permissionsStore.permissions.find(
(permission) => permission.collection === 'directus_roles' && permission.action === 'read'
);
return !!usersCreatePermission && !!rolesReadPermission;
});
/**
* @NOTE
* The userStore contains the information about the role of the current user. We want to
* update the userstore to make sure the role information is accurate with the latest changes
* in case we're changing the current user's role
*/
async function saveAndStay() {
await save();
await userStore.hydrate();
revisionsDrawerDetailRef.value?.refresh?.();
}
async function saveAndQuit() {
await save();
await userStore.hydrate();
router.push(`/settings/roles`);
}
async function deleteAndQuit() {
await remove();
edits.value = {};
router.replace(`/settings/roles`);
}
function discardAndLeave() {
if (!leaveTo.value) return;
edits.value = {};
confirmLeave.value = false;
router.push(leaveTo.value);
}
</script>
<style lang="scss" scoped>

View File

@@ -6,56 +6,46 @@
</div>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, PropType, ref } from 'vue';
import { Permission } from '@directus/types';
<script setup lang="ts">
import api from '@/api';
import { useRouter } from 'vue-router';
import { unexpectedError } from '@/utils/unexpected-error';
import { isPermissionEmpty } from '@/utils/is-permission-empty';
import { unexpectedError } from '@/utils/unexpected-error';
import { Permission } from '@directus/types';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
export default defineComponent({
props: {
roleKey: {
type: String,
default: null,
},
permission: {
type: Object as PropType<Permission>,
required: true,
},
},
emits: ['refresh'],
setup(props, { emit }) {
const { t } = useI18n();
const props = defineProps<{
permission: Permission;
roleKey?: string;
}>();
const router = useRouter();
const emit = defineEmits(['refresh']);
const loading = ref(false);
const { t } = useI18n();
return { t, save, loading };
const router = useRouter();
async function save() {
loading.value = true;
const loading = ref(false);
try {
if (isPermissionEmpty(props.permission)) {
await api.delete(`/permissions/${props.permission.id}`);
} else {
await api.patch(`/permissions/${props.permission.id}`, props.permission);
}
async function save() {
loading.value = true;
emit('refresh');
router.push(`/settings/roles/${props.roleKey || 'public'}`);
} catch (err: any) {
unexpectedError(err);
} finally {
loading.value = false;
}
try {
if (isPermissionEmpty(props.permission)) {
await api.delete(`/permissions/${props.permission.id}`);
} else {
await api.patch(`/permissions/${props.permission.id}`, props.permission);
}
},
});
emit('refresh');
router.push(`/settings/roles/${props.roleKey || 'public'}`);
} catch (err: any) {
unexpectedError(err);
} finally {
loading.value = false;
}
}
</script>
<style lang="scss" scoped>

View File

@@ -25,74 +25,60 @@
</div>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, PropType, computed } from 'vue';
import { Permission, Role } from '@directus/types';
import { Field } from '@directus/types';
import { useSync } from '@directus/composables';
<script setup lang="ts">
import { useFieldsStore } from '@/stores/fields';
import { useSync } from '@directus/composables';
import { Field, Permission, Role } from '@directus/types';
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
export default defineComponent({
props: {
permission: {
type: Object as PropType<Permission>,
required: true,
},
role: {
type: Object as PropType<Role>,
default: null,
},
appMinimal: {
type: Object as PropType<Partial<Permission>>,
default: undefined,
},
const props = defineProps<{
permission: Permission;
role?: Role;
appMinimal?: Partial<Permission>;
}>();
const emit = defineEmits(['update:permission']);
const { t } = useI18n();
const fieldsStore = useFieldsStore();
const internalPermission = useSync(props, 'permission', emit);
const fieldsInCollection = computed(() => {
const fields = fieldsStore.getFieldsForCollectionSorted(props.permission.collection);
return fields.map((field: Field) => {
return {
text: field.name,
value: field.field,
};
});
});
const fields = computed({
get() {
if (!internalPermission.value.fields) return [];
if (internalPermission.value.fields.includes('*')) {
return fieldsInCollection.value.map(({ value }: { value: string }) => value);
}
return internalPermission.value.fields;
},
emits: ['update:permission'],
setup(props, { emit }) {
const { t } = useI18n();
const fieldsStore = useFieldsStore();
const internalPermission = useSync(props, 'permission', emit);
const fieldsInCollection = computed(() => {
const fields = fieldsStore.getFieldsForCollectionSorted(props.permission.collection);
return fields.map((field: Field) => {
return {
text: field.name,
value: field.field,
};
});
});
const fields = computed({
get() {
if (!internalPermission.value.fields) return [];
if (internalPermission.value.fields.includes('*')) {
return fieldsInCollection.value.map(({ value }: { value: string }) => value);
}
return internalPermission.value.fields;
},
set(newFields: string[] | null) {
if (newFields && newFields.length > 0) {
internalPermission.value = {
...internalPermission.value,
fields: newFields,
};
} else {
internalPermission.value = {
...internalPermission.value,
fields: null,
};
}
},
});
return { t, fields, fieldsInCollection };
set(newFields: string[] | null) {
if (newFields && newFields.length > 0) {
internalPermission.value = {
...internalPermission.value,
fields: newFields,
};
} else {
internalPermission.value = {
...internalPermission.value,
fields: null,
};
}
},
});
</script>

View File

@@ -19,50 +19,37 @@
</div>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, PropType, computed } from 'vue';
import { Permission, Role } from '@directus/types';
<script setup lang="ts">
import { useSync } from '@directus/composables';
import { Permission, Role } from '@directus/types';
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
export default defineComponent({
props: {
permission: {
type: Object as PropType<Permission>,
required: true,
},
role: {
type: Object as PropType<Role>,
default: null,
},
appMinimal: {
type: Object as PropType<Partial<Permission>>,
default: undefined,
},
},
emits: ['update:permission'],
setup(props, { emit }) {
const { t } = useI18n();
const props = defineProps<{
permission: Permission;
role?: Role;
appMinimal?: Partial<Permission>;
}>();
const permissionSync = useSync(props, 'permission', emit);
const emit = defineEmits(['update:permission']);
const fields = computed(() => [
{
field: 'permissions',
name: t('rule'),
type: 'json',
meta: {
interface: 'system-filter',
options: {
collectionName: permissionSync.value.collection,
},
},
const { t } = useI18n();
const permissionSync = useSync(props, 'permission', emit);
const fields = computed(() => [
{
field: 'permissions',
name: t('rule'),
type: 'json',
meta: {
interface: 'system-filter',
options: {
collectionName: permissionSync.value.collection,
},
]);
return { t, fields, permissionSync };
},
},
});
]);
</script>
<style lang="scss" scoped>

View File

@@ -12,42 +12,32 @@
</div>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, PropType, computed } from 'vue';
import { Permission, Role } from '@directus/types';
<script setup lang="ts">
import { useSync } from '@directus/composables';
import { Permission, Role } from '@directus/types';
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
export default defineComponent({
props: {
permission: {
type: Object as PropType<Permission>,
default: null,
},
role: {
type: Object as PropType<Role>,
default: null,
},
const props = defineProps<{
permission: Permission;
role?: Role;
}>();
const emit = defineEmits(['update:permission']);
const { t } = useI18n();
const internalPermission = useSync(props, 'permission', emit);
const presets = computed({
get() {
return internalPermission.value.presets;
},
emits: ['update:permission'],
setup(props, { emit }) {
const { t } = useI18n();
const internalPermission = useSync(props, 'permission', emit);
const presets = computed({
get() {
return internalPermission.value.presets;
},
set(newPresets: Record<string, any> | null) {
internalPermission.value = {
...internalPermission.value,
presets: newPresets,
};
},
});
return { t, presets };
set(newPresets: Record<string, any> | null) {
internalPermission.value = {
...internalPermission.value,
presets: newPresets,
};
},
});
</script>

View File

@@ -7,28 +7,17 @@
</v-tabs>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script setup lang="ts">
import { useSync } from '@directus/composables';
export default defineComponent({
props: {
currentTab: {
type: Array,
default: null,
},
tabs: {
type: Array,
required: true,
},
},
emits: ['update:currentTab'],
setup(props, { emit }) {
const internalCurrentTab = useSync(props, 'currentTab', emit);
const props = defineProps<{
tabs: [];
currentTab?: [];
}>();
return { internalCurrentTab };
},
});
const emit = defineEmits(['update:currentTab']);
const internalCurrentTab = useSync(props, 'currentTab', emit);
</script>
<style lang="scss" scoped>

View File

@@ -13,48 +13,38 @@
</div>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, PropType, computed } from 'vue';
import { Permission, Role } from '@directus/types';
<script setup lang="ts">
import { useSync } from '@directus/composables';
import { Permission, Role } from '@directus/types';
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
export default defineComponent({
props: {
permission: {
type: Object as PropType<Permission>,
required: true,
},
role: {
type: Object as PropType<Role>,
default: null,
},
},
emits: ['update:permission'],
setup(props, { emit }) {
const { t } = useI18n();
const props = defineProps<{
permission: Permission;
role?: Role;
}>();
const permissionSync = useSync(props, 'permission', emit);
const emit = defineEmits(['update:permission']);
const fields = computed(() => [
{
field: 'validation',
name: t('rule'),
type: 'json',
meta: {
interface: 'system-filter',
options: {
collectionName: permissionSync.value.collection,
includeValidation: true,
includeRelations: false,
},
},
const { t } = useI18n();
const permissionSync = useSync(props, 'permission', emit);
const fields = computed(() => [
{
field: 'validation',
name: t('rule'),
type: 'json',
meta: {
interface: 'system-filter',
options: {
collectionName: permissionSync.value.collection,
includeValidation: true,
includeRelations: false,
},
]);
return { t, permissionSync, fields };
},
},
});
]);
</script>
<style lang="scss" scoped>

View File

@@ -8,7 +8,7 @@
@cancel="close"
>
<template v-if="!loading" #sidebar>
<tabs v-model:current-tab="currentTab" :tabs="tabs" />
<tabs v-model:current-tab="currentTab" :tabs="tabsValue" />
</template>
<div v-if="!loading" class="content">
@@ -44,175 +44,162 @@
</v-drawer>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, ref, computed, watch } from 'vue';
<script setup lang="ts">
import api from '@/api';
import { Permission, Role } from '@directus/types';
import { useCollectionsStore } from '@/stores/collections';
import { useRouter } from 'vue-router';
import Actions from './components/actions.vue';
import Tabs from './components/tabs.vue';
import Permissions from './components/permissions.vue';
import Fields from './components/fields.vue';
import Validation from './components/validation.vue';
import Presets from './components/presets.vue';
import { unexpectedError } from '@/utils/unexpected-error';
import { appMinimalPermissions } from '../app-permissions';
import { useDialogRoute } from '@/composables/use-dialog-route';
import { useCollectionsStore } from '@/stores/collections';
import { isPermissionEmpty } from '@/utils/is-permission-empty';
import { unexpectedError } from '@/utils/unexpected-error';
import { Permission, Role } from '@directus/types';
import { computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { appMinimalPermissions } from '../app-permissions';
import Actions from './components/actions.vue';
import Fields from './components/fields.vue';
import Permissions from './components/permissions.vue';
import Presets from './components/presets.vue';
import Tabs from './components/tabs.vue';
import Validation from './components/validation.vue';
export default defineComponent({
components: { Actions, Tabs, Permissions, Fields, Validation, Presets },
props: {
roleKey: {
type: String,
default: null,
},
permissionKey: {
type: String,
required: true,
},
},
emits: ['refresh'],
setup(props) {
const { t } = useI18n();
const props = defineProps<{
permissionKey: string;
roleKey?: string;
}>();
const router = useRouter();
defineEmits(['refresh']);
const collectionsStore = useCollectionsStore();
const { t } = useI18n();
const isOpen = useDialogRoute();
const router = useRouter();
const permission = ref<Permission>();
const role = ref<Role>();
const loading = ref(false);
const collectionsStore = useCollectionsStore();
const collectionName = computed(() => {
if (!permission.value) return null;
return collectionsStore.collections.find((collection) => collection.collection === permission.value!.collection)
?.name;
});
const isOpen = useDialogRoute();
const modalTitle = computed(() => {
if (loading.value || !permission.value) return t('loading');
const permission = ref<Permission>();
const role = ref<Role>();
const loading = ref(false);
if (props.roleKey) {
return role.value!.name + ' -> ' + collectionName.value + ' -> ' + t(permission.value.action);
}
return t('public_label') + ' -> ' + collectionName.value + ' -> ' + t(permission.value.action);
});
watch(() => props.permissionKey, load, { immediate: true });
const tabs = computed(() => {
if (!permission.value) return [];
const action = permission.value.action;
const tabs = [];
if (['read', 'update', 'delete', 'share'].includes(action)) {
tabs.push({
text: t('item_permissions'),
value: 'permissions',
hasValue: permission.value.permissions !== null && Object.keys(permission.value.permissions).length > 0,
});
}
if (['create', 'read', 'update'].includes(action)) {
tabs.push({
text: t('field_permissions'),
value: 'fields',
hasValue: permission.value.fields !== null,
});
}
if (['create', 'update'].includes(action)) {
tabs.push({
text: t('field_validation'),
value: 'validation',
hasValue: permission.value.validation !== null && Object.keys(permission.value.validation).length > 0,
});
}
if (['create', 'update'].includes(action)) {
tabs.push({
text: t('field_presets'),
value: 'presets',
hasValue: permission.value.presets !== null && Object.keys(permission.value.presets).length > 0,
});
}
return tabs;
});
const currentTab = ref<string[]>([]);
const currentTabInfo = computed(() => {
const tabKey = currentTab.value?.[0];
if (!tabKey) return null;
return tabs.value.find((tab) => tab.value === tabKey);
});
watch(
tabs,
(newTabs, oldTabs) => {
if (newTabs && oldTabs && newTabs.length === oldTabs.length) return;
currentTab.value = [tabs?.value?.[0]?.value];
},
{ immediate: true }
);
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 { isOpen, permission, role, loading, modalTitle, tabs, currentTab, currentTabInfo, appMinimal, close };
async function close() {
if (permission.value && isPermissionEmpty(permission.value)) {
await api.delete(`/permissions/${permission.value.id}`);
router.replace(`/settings/roles/${props.roleKey || 'public'}`);
} else {
router.push(`/settings/roles/${props.roleKey || 'public'}`);
}
}
async function load() {
loading.value = true;
try {
if (props.roleKey) {
const response = await api.get(`/roles/${props.roleKey}`, {
params: {
deep: { users: { _limit: 0 } },
},
});
role.value = response.data.data;
}
const response = await api.get(`/permissions/${props.permissionKey}`);
permission.value = response.data.data;
} catch (err: any) {
if (err?.response?.status === 403) {
router.push(`/settings/roles/${props.roleKey || 'public'}`);
} else {
unexpectedError(err);
}
} finally {
loading.value = false;
}
}
},
const collectionName = computed(() => {
if (!permission.value) return null;
return collectionsStore.collections.find((collection) => collection.collection === permission.value!.collection)
?.name;
});
const modalTitle = computed(() => {
if (loading.value || !permission.value) return t('loading');
if (props.roleKey) {
return role.value!.name + ' -> ' + collectionName.value + ' -> ' + t(permission.value.action);
}
return t('public_label') + ' -> ' + collectionName.value + ' -> ' + t(permission.value.action);
});
watch(() => props.permissionKey, load, { immediate: true });
const tabsValue = computed(() => {
if (!permission.value) return [];
const action = permission.value.action;
const tabs = [];
if (['read', 'update', 'delete', 'share'].includes(action)) {
tabs.push({
text: t('item_permissions'),
value: 'permissions',
hasValue: permission.value.permissions !== null && Object.keys(permission.value.permissions).length > 0,
});
}
if (['create', 'read', 'update'].includes(action)) {
tabs.push({
text: t('field_permissions'),
value: 'fields',
hasValue: permission.value.fields !== null,
});
}
if (['create', 'update'].includes(action)) {
tabs.push({
text: t('field_validation'),
value: 'validation',
hasValue: permission.value.validation !== null && Object.keys(permission.value.validation).length > 0,
});
}
if (['create', 'update'].includes(action)) {
tabs.push({
text: t('field_presets'),
value: 'presets',
hasValue: permission.value.presets !== null && Object.keys(permission.value.presets).length > 0,
});
}
return tabs;
});
const currentTab = ref<string[]>([]);
const currentTabInfo = computed(() => {
const tabKey = currentTab.value?.[0];
if (!tabKey) return null;
return tabsValue.value.find((tab) => tab.value === tabKey);
});
watch(
tabsValue,
(newTabs, oldTabs) => {
if (newTabs && oldTabs && newTabs.length === oldTabs.length) return;
currentTab.value = [tabsValue?.value?.[0]?.value];
},
{ immediate: true }
);
const appMinimal = computed(() => {
if (!permission.value) return null;
return appMinimalPermissions.find(
(p: Partial<Permission>) => p.collection === permission.value!.collection && p.action === permission.value!.action
);
});
async function close() {
if (permission.value && isPermissionEmpty(permission.value)) {
await api.delete(`/permissions/${permission.value.id}`);
router.replace(`/settings/roles/${props.roleKey || 'public'}`);
} else {
router.push(`/settings/roles/${props.roleKey || 'public'}`);
}
}
async function load() {
loading.value = true;
try {
if (props.roleKey) {
const response = await api.get(`/roles/${props.roleKey}`, {
params: {
deep: { users: { _limit: 0 } },
},
});
role.value = response.data.data;
}
const response = await api.get(`/permissions/${props.permissionKey}`);
permission.value = response.data.data;
} catch (err: any) {
if (err?.response?.status === 403) {
router.push(`/settings/roles/${props.roleKey || 'public'}`);
} else {
unexpectedError(err);
}
} finally {
loading.value = false;
}
}
</script>
<style lang="scss" scoped>

View File

@@ -21,28 +21,17 @@
</private-view>
</template>
<script lang="ts">
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent } from 'vue';
import SettingsNavigation from '../../components/navigation.vue';
import PermissionsOverview from './item/components/permissions-overview.vue';
import RoleInfoSidebarDetail from './item/components/role-info-sidebar-detail.vue';
export default defineComponent({
name: 'RolesItem',
components: { SettingsNavigation, PermissionsOverview, RoleInfoSidebarDetail },
props: {
permissionKey: {
type: String,
default: null,
},
},
setup() {
const { t } = useI18n();
return { t };
},
});
defineProps<{
permissionKey?: string;
}>();
const { t } = useI18n();
</script>
<style lang="scss" scoped>

View File

@@ -95,103 +95,78 @@
</component>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, computed, ref } from 'vue';
import SettingsNavigation from '../../components/navigation.vue';
import LayoutSidebarDetail from '@/views/private/components/layout-sidebar-detail.vue';
import { usePreset } from '@/composables/use-preset';
import { useLayout } from '@directus/composables';
<script setup lang="ts">
import api from '@/api';
import SearchInput from '@/views/private/components/search-input.vue';
import { useExtension } from '@/composables/use-extension';
import { usePreset } from '@/composables/use-preset';
import LayoutSidebarDetail from '@/views/private/components/layout-sidebar-detail.vue';
import SearchInput from '@/views/private/components/search-input.vue';
import { useLayout } from '@directus/composables';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import SettingsNavigation from '../../components/navigation.vue';
type Item = {
[field: string]: any;
};
export default defineComponent({
name: 'WebhooksCollection',
components: { SettingsNavigation, LayoutSidebarDetail, SearchInput },
setup() {
const { t } = useI18n();
const { t } = useI18n();
const layoutRef = ref();
const selection = ref<Item[]>([]);
const layoutRef = ref();
const selection = ref<Item[]>([]);
const { layout, layoutOptions, layoutQuery, filter, search } = usePreset(ref('directus_webhooks'));
const { addNewLink, batchLink } = useLinks();
const { confirmDelete, deleting, batchDelete } = useBatchDelete();
const { layout, layoutOptions, layoutQuery, filter, search } = usePreset(ref('directus_webhooks'));
const { addNewLink, batchLink } = useLinks();
const { confirmDelete, deleting, batchDelete } = useBatchDelete();
const { layoutWrapper } = useLayout(layout);
const { layoutWrapper } = useLayout(layout);
const currentLayout = useExtension('layout', layout);
const currentLayout = useExtension('layout', layout);
return {
t,
addNewLink,
batchDelete,
batchLink,
confirmDelete,
deleting,
layoutRef,
layoutWrapper,
filter,
selection,
layoutOptions,
layoutQuery,
layout,
search,
clearFilters,
currentLayout,
};
async function refresh() {
await layoutRef.value?.state?.refresh?.();
}
async function refresh() {
await layoutRef.value?.state?.refresh?.();
}
function useBatchDelete() {
const confirmDelete = ref(false);
const deleting = ref(false);
function useBatchDelete() {
const confirmDelete = ref(false);
const deleting = ref(false);
return { confirmDelete, deleting, batchDelete };
return { confirmDelete, deleting, batchDelete };
async function batchDelete() {
deleting.value = true;
async function batchDelete() {
deleting.value = true;
confirmDelete.value = false;
confirmDelete.value = false;
const batchPrimaryKeys = selection.value;
const batchPrimaryKeys = selection.value;
await api.delete(`/webhooks/${batchPrimaryKeys}`);
await api.delete(`/webhooks/${batchPrimaryKeys}`);
await refresh();
await refresh();
selection.value = [];
deleting.value = false;
confirmDelete.value = false;
}
}
selection.value = [];
deleting.value = false;
confirmDelete.value = false;
}
}
function useLinks() {
const addNewLink = computed<string>(() => {
return `/settings/webhooks/+`;
});
function useLinks() {
const addNewLink = computed<string>(() => {
return `/settings/webhooks/+`;
});
const batchLink = computed<string>(() => {
const batchPrimaryKeys = selection.value;
return `/settings/webhooks/${batchPrimaryKeys}`;
});
const batchLink = computed<string>(() => {
const batchPrimaryKeys = selection.value;
return `/settings/webhooks/${batchPrimaryKeys}`;
});
return { addNewLink, batchLink };
}
return { addNewLink, batchLink };
}
function clearFilters() {
filter.value = null;
search.value = null;
}
},
});
function clearFilters() {
filter.value = null;
search.value = null;
}
</script>
<style lang="scss" scoped>

View File

@@ -88,135 +88,87 @@
</private-view>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, computed, toRefs, ref } from 'vue';
import SettingsNavigation from '../../components/navigation.vue';
import { useRouter } from 'vue-router';
import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail.vue';
import { useItem } from '@/composables/use-item';
import SaveOptions from '@/views/private/components/save-options.vue';
import { useShortcut } from '@/composables/use-shortcut';
<script setup lang="ts">
import { useEditsGuard } from '@/composables/use-edits-guard';
import { useItem } from '@/composables/use-item';
import { useShortcut } from '@/composables/use-shortcut';
import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail.vue';
import SaveOptions from '@/views/private/components/save-options.vue';
import { computed, ref, toRefs } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import SettingsNavigation from '../../components/navigation.vue';
export default defineComponent({
name: 'WebhooksItem',
components: { SettingsNavigation, RevisionsDrawerDetail, SaveOptions },
props: {
primaryKey: {
type: String,
required: true,
},
},
setup(props) {
const { t } = useI18n();
const props = defineProps<{
primaryKey: string;
}>();
const router = useRouter();
const { t } = useI18n();
const { primaryKey } = toRefs(props);
const router = useRouter();
const revisionsDrawerDetailRef = ref<InstanceType<typeof RevisionsDrawerDetail> | null>(null);
const { primaryKey } = toRefs(props);
const {
isNew,
edits,
hasEdits,
item,
saving,
loading,
error,
save,
remove,
deleting,
saveAsCopy,
isBatch,
validationErrors,
} = useItem(ref('directus_webhooks'), primaryKey);
const revisionsDrawerDetailRef = ref<InstanceType<typeof RevisionsDrawerDetail> | null>(null);
const confirmDelete = ref(false);
const { isNew, edits, hasEdits, item, saving, loading, save, remove, deleting, saveAsCopy, isBatch, validationErrors } =
useItem(ref('directus_webhooks'), primaryKey);
const title = computed(() => {
if (loading.value) return t('loading');
if (isNew.value) return t('creating_webhook');
return item.value?.name;
});
const confirmDelete = ref(false);
useShortcut('meta+s', () => {
if (hasEdits.value) saveAndStay();
});
useShortcut('meta+shift+s', () => {
if (hasEdits.value) saveAndAddNew();
});
const { confirmLeave, leaveTo } = useEditsGuard(hasEdits);
return {
t,
item,
loading,
error,
isNew,
edits,
hasEdits,
saving,
saveAndQuit,
deleteAndQuit,
confirmDelete,
deleting,
saveAndStay,
saveAndAddNew,
saveAsCopyAndNavigate,
discardAndStay,
isBatch,
title,
validationErrors,
confirmLeave,
leaveTo,
discardAndLeave,
revisionsDrawerDetailRef,
};
async function saveAndQuit() {
await save();
router.push(`/settings/webhooks`);
}
async function saveAndStay() {
await save();
revisionsDrawerDetailRef.value?.refresh?.();
}
async function saveAndAddNew() {
await save();
router.push(`/settings/webhooks/+`);
}
async function saveAsCopyAndNavigate() {
const newPrimaryKey = await saveAsCopy();
if (newPrimaryKey) router.push(`/settings/webhooks/${newPrimaryKey}`);
}
async function deleteAndQuit() {
await remove();
edits.value = {};
router.replace(`/settings/webhooks`);
}
function discardAndLeave() {
if (!leaveTo.value) return;
edits.value = {};
confirmLeave.value = false;
router.push(leaveTo.value);
}
function discardAndStay() {
edits.value = {};
confirmLeave.value = false;
}
},
const title = computed(() => {
if (loading.value) return t('loading');
if (isNew.value) return t('creating_webhook');
return item.value?.name;
});
useShortcut('meta+s', () => {
if (hasEdits.value) saveAndStay();
});
useShortcut('meta+shift+s', () => {
if (hasEdits.value) saveAndAddNew();
});
const { confirmLeave, leaveTo } = useEditsGuard(hasEdits);
async function saveAndQuit() {
await save();
router.push(`/settings/webhooks`);
}
async function saveAndStay() {
await save();
revisionsDrawerDetailRef.value?.refresh?.();
}
async function saveAndAddNew() {
await save();
router.push(`/settings/webhooks/+`);
}
async function saveAsCopyAndNavigate() {
const newPrimaryKey = await saveAsCopy();
if (newPrimaryKey) router.push(`/settings/webhooks/${newPrimaryKey}`);
}
async function deleteAndQuit() {
await remove();
edits.value = {};
router.replace(`/settings/webhooks`);
}
function discardAndLeave() {
if (!leaveTo.value) return;
edits.value = {};
confirmLeave.value = false;
router.push(leaveTo.value);
}
function discardAndStay() {
edits.value = {};
confirmLeave.value = false;
}
</script>
<style lang="scss" scoped>