forked from verdnatura/salix-front
Compare commits
2 Commits
dev
...
7822-check
Author | SHA1 | Date |
---|---|---|
Alex Moreno | 9d3569ff6e | |
Alex Moreno | bb85e8b3cb |
|
@ -3,6 +3,8 @@ import qFormMixin from './qformMixin';
|
||||||
import mainShortcutMixin from './mainShortcutMixin';
|
import mainShortcutMixin from './mainShortcutMixin';
|
||||||
import keyShortcut from './keyShortcut';
|
import keyShortcut from './keyShortcut';
|
||||||
import useNotify from 'src/composables/useNotify.js';
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
import { AccessError } from 'src/utils/errors';
|
||||||
|
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
|
|
||||||
export default boot(({ app }) => {
|
export default boot(({ app }) => {
|
||||||
|
@ -11,6 +13,12 @@ export default boot(({ app }) => {
|
||||||
app.directive('shortcut', keyShortcut);
|
app.directive('shortcut', keyShortcut);
|
||||||
app.config.errorHandler = function (err) {
|
app.config.errorHandler = function (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
switch (err.constructor) {
|
||||||
|
case AccessError:
|
||||||
|
notify('errors.statusUnauthorized', 'negative', 'account_circle_off');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
notify('globals.error', 'negative', 'error');
|
notify('globals.error', 'negative', 'error');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch, onMounted } from 'vue';
|
||||||
import { useRouter, onBeforeRouteLeave } from 'vue-router';
|
import { useRouter, onBeforeRouteLeave } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
|
@ -10,12 +10,14 @@ import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||||
import SkeletonTable from 'components/ui/SkeletonTable.vue';
|
import SkeletonTable from 'components/ui/SkeletonTable.vue';
|
||||||
import { tMobile } from 'src/composables/tMobile';
|
import { tMobile } from 'src/composables/tMobile';
|
||||||
|
import { useAcl } from 'src/composables/useAcl';
|
||||||
|
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const stateStore = useStateStore();
|
const stateStore = useStateStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { validate } = useValidator();
|
const { validate } = useValidator();
|
||||||
|
const { checkUrl } = useAcl();
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
model: {
|
model: {
|
||||||
|
@ -71,6 +73,10 @@ const $props = defineProps({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
checkPermissions: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
@ -80,6 +86,9 @@ const vnPaginateRef = ref();
|
||||||
const formData = ref();
|
const formData = ref();
|
||||||
const saveButtonRef = ref(null);
|
const saveButtonRef = ref(null);
|
||||||
const watchChanges = ref();
|
const watchChanges = ref();
|
||||||
|
const saveChangeUrl = ref();
|
||||||
|
const hasWritePremission = ref();
|
||||||
|
const accessDeniedTitle = ref(t('errors.statusUnauthorized'));
|
||||||
const formUrl = computed(() => $props.url);
|
const formUrl = computed(() => $props.url);
|
||||||
|
|
||||||
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
||||||
|
@ -95,6 +104,8 @@ defineExpose({
|
||||||
getChanges,
|
getChanges,
|
||||||
formData,
|
formData,
|
||||||
vnPaginateRef,
|
vnPaginateRef,
|
||||||
|
hasWritePremission,
|
||||||
|
accessDeniedTitle,
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeRouteLeave((to, from, next) => {
|
onBeforeRouteLeave((to, from, next) => {
|
||||||
|
@ -110,6 +121,17 @@ onBeforeRouteLeave((to, from, next) => {
|
||||||
else next();
|
else next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
saveChangeUrl.value = $props.saveUrl || $props.url + '/crud';
|
||||||
|
hasWritePremission.value =
|
||||||
|
!$props.checkPermissions || checkUrl(saveChangeUrl.value, 'post');
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(formUrl, async () => {
|
||||||
|
originalData.value = null;
|
||||||
|
reset();
|
||||||
|
});
|
||||||
|
|
||||||
async function fetch(data) {
|
async function fetch(data) {
|
||||||
resetData(data);
|
resetData(data);
|
||||||
emit('onFetch', data);
|
emit('onFetch', data);
|
||||||
|
@ -173,7 +195,7 @@ async function saveChanges(data) {
|
||||||
}
|
}
|
||||||
const changes = data || getChanges();
|
const changes = data || getChanges();
|
||||||
try {
|
try {
|
||||||
await axios.post($props.saveUrl || $props.url + '/crud', changes);
|
await axios.post(saveChangeUrl.value, changes);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return (isLoading.value = false);
|
return (isLoading.value = false);
|
||||||
}
|
}
|
||||||
|
@ -299,11 +321,6 @@ async function reload(params) {
|
||||||
const data = await vnPaginateRef.value.fetch(params);
|
const data = await vnPaginateRef.value.fetch(params);
|
||||||
fetch(data);
|
fetch(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(formUrl, async () => {
|
|
||||||
originalData.value = null;
|
|
||||||
reset();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VnPaginate
|
<VnPaginate
|
||||||
|
@ -337,8 +354,8 @@ watch(formUrl, async () => {
|
||||||
icon="delete"
|
icon="delete"
|
||||||
flat
|
flat
|
||||||
@click="remove(selected)"
|
@click="remove(selected)"
|
||||||
:disable="!selected?.length"
|
:disable="!selected?.length || !hasWritePremission"
|
||||||
:title="t('globals.remove')"
|
:title="hasWritePremission ? t('globals.remove') : accessDeniedTitle"
|
||||||
v-if="$props.defaultRemove"
|
v-if="$props.defaultRemove"
|
||||||
/>
|
/>
|
||||||
<QBtn
|
<QBtn
|
||||||
|
@ -355,8 +372,10 @@ watch(formUrl, async () => {
|
||||||
v-if="$props.goTo && $props.defaultSave"
|
v-if="$props.goTo && $props.defaultSave"
|
||||||
@click="onSubmitAndGo"
|
@click="onSubmitAndGo"
|
||||||
:label="tMobile('globals.saveAndContinue')"
|
:label="tMobile('globals.saveAndContinue')"
|
||||||
:title="t('globals.saveAndContinue')"
|
:title="
|
||||||
:disable="!hasChanges"
|
hasWritePremission ? t('globals.saveAndContinue') : accessDeniedTitle
|
||||||
|
"
|
||||||
|
:disable="!hasChanges || !hasWritePremission"
|
||||||
color="primary"
|
color="primary"
|
||||||
icon="save"
|
icon="save"
|
||||||
split
|
split
|
||||||
|
@ -390,8 +409,8 @@ watch(formUrl, async () => {
|
||||||
color="primary"
|
color="primary"
|
||||||
icon="save"
|
icon="save"
|
||||||
@click="onSubmit"
|
@click="onSubmit"
|
||||||
:disable="!hasChanges"
|
:disable="!hasChanges || !hasWritePremission"
|
||||||
:title="t('globals.save')"
|
:title="hasWritePremission ? t('globals.save') : accessDeniedTitle"
|
||||||
/>
|
/>
|
||||||
<slot name="moreAfterActions" />
|
<slot name="moreAfterActions" />
|
||||||
</QBtnGroup>
|
</QBtnGroup>
|
||||||
|
|
|
@ -13,6 +13,7 @@ import VnConfirm from './ui/VnConfirm.vue';
|
||||||
import { tMobile } from 'src/composables/tMobile';
|
import { tMobile } from 'src/composables/tMobile';
|
||||||
import { useArrayData } from 'src/composables/useArrayData';
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useAcl } from 'src/composables/useAcl';
|
||||||
|
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
|
@ -21,6 +22,8 @@ const stateStore = useStateStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { validate } = useValidator();
|
const { validate } = useValidator();
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
|
const { checkUrl } = useAcl();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const myForm = ref(null);
|
const myForm = ref(null);
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
|
@ -91,6 +94,10 @@ const $props = defineProps({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
checkPermissions: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const emit = defineEmits(['onFetch', 'onDataSaved']);
|
const emit = defineEmits(['onFetch', 'onDataSaved']);
|
||||||
const modelValue = computed(
|
const modelValue = computed(
|
||||||
|
@ -104,6 +111,15 @@ const isResetting = ref(false);
|
||||||
const hasChanges = ref(!$props.observeFormChanges);
|
const hasChanges = ref(!$props.observeFormChanges);
|
||||||
const originalData = ref({});
|
const originalData = ref({});
|
||||||
const formData = computed(() => state.get(modelValue));
|
const formData = computed(() => state.get(modelValue));
|
||||||
|
const saveUrl = computed(
|
||||||
|
() => $props.urlCreate || $props.urlUpdate || $props.url || arrayData.store.url
|
||||||
|
);
|
||||||
|
const saveMethod = computed(() => ($props.urlCreate ? 'post' : 'patch'));
|
||||||
|
const hasWritePremission = computed(
|
||||||
|
() =>
|
||||||
|
$props.saveFn ||
|
||||||
|
(!$props.checkPermissions && checkUrl(saveUrl.value, saveMethod.value))
|
||||||
|
);
|
||||||
const defaultButtons = computed(() => ({
|
const defaultButtons = computed(() => ({
|
||||||
save: {
|
save: {
|
||||||
color: 'primary',
|
color: 'primary',
|
||||||
|
@ -184,6 +200,7 @@ onUnmounted(() => {
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
try {
|
try {
|
||||||
|
checkUrl($props.url, 'get', true);
|
||||||
let { data } = await axios.get($props.url, {
|
let { data } = await axios.get($props.url, {
|
||||||
params: { filter: JSON.stringify($props.filter) },
|
params: { filter: JSON.stringify($props.filter) },
|
||||||
});
|
});
|
||||||
|
@ -204,13 +221,10 @@ async function save() {
|
||||||
try {
|
try {
|
||||||
formData.value = trimData(formData.value);
|
formData.value = trimData(formData.value);
|
||||||
const body = $props.mapper ? $props.mapper(formData.value) : formData.value;
|
const body = $props.mapper ? $props.mapper(formData.value) : formData.value;
|
||||||
const method = $props.urlCreate ? 'post' : 'patch';
|
|
||||||
const url =
|
|
||||||
$props.urlCreate || $props.urlUpdate || $props.url || arrayData.store.url;
|
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
if ($props.saveFn) response = await $props.saveFn(body);
|
if ($props.saveFn) response = await $props.saveFn(body);
|
||||||
else response = await axios[method](url, body);
|
else response = await axios[saveMethod.value](saveUrl.value, body);
|
||||||
|
|
||||||
if ($props.urlCreate) notify('globals.dataCreated', 'positive');
|
if ($props.urlCreate) notify('globals.dataCreated', 'positive');
|
||||||
|
|
||||||
|
@ -323,7 +337,7 @@ defineExpose({
|
||||||
@click="saveAndGo"
|
@click="saveAndGo"
|
||||||
:label="tMobile('globals.saveAndContinue')"
|
:label="tMobile('globals.saveAndContinue')"
|
||||||
:title="t('globals.saveAndContinue')"
|
:title="t('globals.saveAndContinue')"
|
||||||
:disable="!hasChanges"
|
:disable="!hasChanges || !hasWritePremission"
|
||||||
color="primary"
|
color="primary"
|
||||||
icon="save"
|
icon="save"
|
||||||
split
|
split
|
||||||
|
@ -355,7 +369,7 @@ defineExpose({
|
||||||
color="primary"
|
color="primary"
|
||||||
icon="save"
|
icon="save"
|
||||||
@click="defaultButtons.save.click"
|
@click="defaultButtons.save.click"
|
||||||
:disable="!hasChanges"
|
:disable="!hasChanges || !hasWritePremission"
|
||||||
:title="t(defaultButtons.save.label)"
|
:title="t(defaultButtons.save.label)"
|
||||||
/>
|
/>
|
||||||
</QBtnGroup>
|
</QBtnGroup>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useState } from './useState';
|
import { useState } from './useState';
|
||||||
|
import { AccessError } from 'src/utils/errors';
|
||||||
|
|
||||||
export function useAcl() {
|
export function useAcl() {
|
||||||
const state = useState();
|
const state = useState();
|
||||||
|
@ -8,31 +9,86 @@ export function useAcl() {
|
||||||
const { data } = await axios.get('VnUsers/acls');
|
const { data } = await axios.get('VnUsers/acls');
|
||||||
const acls = {};
|
const acls = {};
|
||||||
data.forEach((acl) => {
|
data.forEach((acl) => {
|
||||||
acls[acl.model] = acls[acl.model] || {};
|
const model = acl.model.toLowerCase();
|
||||||
acls[acl.model][acl.property] = acls[acl.model][acl.property] || {};
|
const property = acl.property.toLowerCase();
|
||||||
acls[acl.model][acl.property][acl.accessType] = true;
|
acls[model] = acls[model] || {};
|
||||||
|
acls[model][property] = acls[model][property] || {};
|
||||||
|
acls[model][property][acl.accessType] = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
state.setAcls(acls);
|
state.setAcls(acls);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasAny(acls) {
|
function hasAny(acls) {
|
||||||
|
let result = false;
|
||||||
for (const acl of acls) {
|
for (const acl of acls) {
|
||||||
let { model, props, accessType } = acl;
|
let { model, props, accessType } = acl;
|
||||||
const modelAcls = state.getAcls().value[model];
|
const modelAcls = state.getAcls().value[model.toLowerCase()];
|
||||||
Array.isArray(props) || (props = [props]);
|
Array.isArray(props) || (props = [props]);
|
||||||
if (modelAcls)
|
if (modelAcls) {
|
||||||
return ['*', ...props].some((key) => {
|
result = ['*', ...props].some((key) => {
|
||||||
const acl = modelAcls[key];
|
const acl = modelAcls[key.toLowerCase()];
|
||||||
return acl && (acl['*'] || acl[accessType]);
|
return acl && (acl['*'] || acl[accessType]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return false;
|
if (result) return result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkRead(model, urlSplit) {
|
||||||
|
const acls = [];
|
||||||
|
let props = urlSplit[1] ?? 'find';
|
||||||
|
if (typeof +props == 'number') {
|
||||||
|
props = urlSplit[2] ?? 'findById';
|
||||||
|
acls.push({ model, props: `__get__${props}`, accessType: 'READ' });
|
||||||
|
}
|
||||||
|
|
||||||
|
acls.push({ model, props, accessType: 'READ' });
|
||||||
|
console.log('acls: ', acls);
|
||||||
|
console.log('hasAny(acls): ', hasAny(acls));
|
||||||
|
return hasAny(acls);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkWrite(model, urlSplit, type) {
|
||||||
|
const acls = [];
|
||||||
|
let props = urlSplit[1] ?? (type != 'post' ? 'upsert' : 'create');
|
||||||
|
|
||||||
|
if (typeof +props == 'number') {
|
||||||
|
if (!urlSplit[2]) props = 'updateAttributes';
|
||||||
|
else if (['post', 'delete'].includes(type)) {
|
||||||
|
let prefix = 'create';
|
||||||
|
if (type == 'delete') prefix = type;
|
||||||
|
acls.push({ model, props: `__${prefix}__${props}`, accessType: 'WRITE' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
acls.push({ model, props, accessType: 'WRITE' });
|
||||||
|
console.log('acls: ', acls);
|
||||||
|
console.log('hasAny(acls): ', hasAny(acls));
|
||||||
|
return hasAny(acls);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkUrl(url, type = 'get', throwError, model) {
|
||||||
|
if (!url) return true;
|
||||||
|
|
||||||
|
const urlSplit = url.split('/');
|
||||||
|
model ??= urlSplit[0]?.slice(1, -1);
|
||||||
|
type = type.toLowerCase();
|
||||||
|
let hasPermission;
|
||||||
|
if (type == 'get') hasPermission = checkRead(model, urlSplit);
|
||||||
|
else hasPermission = checkWrite(model, urlSplit, type);
|
||||||
|
|
||||||
|
if (throwError && !hasPermission) throw new AccessError(url);
|
||||||
|
return hasPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fetch,
|
fetch,
|
||||||
hasAny,
|
hasAny,
|
||||||
state,
|
state,
|
||||||
|
checkRead,
|
||||||
|
checkWrite,
|
||||||
|
checkUrl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { useRouter, useRoute } from 'vue-router';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useArrayDataStore } from 'stores/useArrayDataStore';
|
import { useArrayDataStore } from 'stores/useArrayDataStore';
|
||||||
import { buildFilter } from 'filters/filterPanel';
|
import { buildFilter } from 'filters/filterPanel';
|
||||||
|
import { useAcl } from 'src/composables/useAcl';
|
||||||
|
|
||||||
const arrayDataStore = useArrayDataStore();
|
const arrayDataStore = useArrayDataStore();
|
||||||
|
|
||||||
|
@ -14,6 +15,8 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
const store = arrayDataStore.get(key);
|
const store = arrayDataStore.get(key);
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { checkUrl } = useAcl();
|
||||||
|
|
||||||
let canceller = null;
|
let canceller = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
@ -65,6 +68,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
|
|
||||||
async function fetch({ append = false, updateRouter = true }) {
|
async function fetch({ append = false, updateRouter = true }) {
|
||||||
if (!store.url) return;
|
if (!store.url) return;
|
||||||
|
if (store.checkAcl) checkUrl(store.url, 'get', true);
|
||||||
|
|
||||||
cancelRequest();
|
cancelRequest();
|
||||||
canceller = new AbortController();
|
canceller = new AbortController();
|
||||||
|
|
|
@ -54,7 +54,7 @@ export function useSession() {
|
||||||
headers: { Authorization: storage.getItem(key) },
|
headers: { Authorization: storage.getItem(key) },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notify('errors.statusUnauthorized', 'negative');
|
notify('errors.statusUnauthorized', 'negative', 'account_circle_off');
|
||||||
} finally {
|
} finally {
|
||||||
storage.removeItem(key);
|
storage.removeItem(key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ const route = useRoute();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const claimDevelopmentForm = ref();
|
const claimDevelopmentForm = ref({});
|
||||||
const claimReasons = ref([]);
|
const claimReasons = ref([]);
|
||||||
const claimResults = ref([]);
|
const claimResults = ref([]);
|
||||||
const claimResponsibles = ref([]);
|
const claimResponsibles = ref([]);
|
||||||
|
@ -222,9 +222,16 @@ const columns = computed(() => [
|
||||||
ref="saveButtonRef"
|
ref="saveButtonRef"
|
||||||
color="primary"
|
color="primary"
|
||||||
icon="save"
|
icon="save"
|
||||||
:disable="!claimDevelopmentForm?.hasChanges"
|
:disable="
|
||||||
|
!claimDevelopmentForm?.hasChanges ||
|
||||||
|
!claimDevelopmentForm.hasWritePremission
|
||||||
|
"
|
||||||
@click="claimDevelopmentForm?.onSubmit"
|
@click="claimDevelopmentForm?.onSubmit"
|
||||||
:title="t('globals.save')"
|
:title="
|
||||||
|
claimDevelopmentForm.hasWritePremission
|
||||||
|
? t('globals.save')
|
||||||
|
: claimDevelopmentForm.accessDeniedTitle
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</CrudModel>
|
</CrudModel>
|
||||||
|
@ -235,6 +242,11 @@ const columns = computed(() => [
|
||||||
icon="add"
|
icon="add"
|
||||||
@keydown.tab.prevent="saveButtonRef.$el.focus()"
|
@keydown.tab.prevent="saveButtonRef.$el.focus()"
|
||||||
@click="claimDevelopmentForm.insert()"
|
@click="claimDevelopmentForm.insert()"
|
||||||
|
:disable="!claimDevelopmentForm.hasWritePremission"
|
||||||
|
:title="
|
||||||
|
!claimDevelopmentForm.hasWritePremission &&
|
||||||
|
claimDevelopmentForm.accessDeniedTitle
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</QPageSticky>
|
</QPageSticky>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -17,6 +17,7 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => {
|
||||||
searchUrl: 'params',
|
searchUrl: 'params',
|
||||||
navigate: null,
|
navigate: null,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
checkAcl: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
function get(key) {
|
function get(key) {
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
class AccessError extends Error {
|
||||||
|
constructor(details, message) {
|
||||||
|
super(message ?? 'Access denied');
|
||||||
|
this.name = AccessError.name;
|
||||||
|
this.details = details;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { AccessError };
|
Loading…
Reference in New Issue