mirror of
https://github.com/directus/directus.git
synced 2026-01-25 13:38:02 -05:00
script[setup]: rest of settings (#18447)
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user