mirror of
https://github.com/45Drives/cockpit-navigator.git
synced 2025-07-31 01:24:37 +02:00
add editing file permissions
This commit is contained in:
parent
06c6a04d59
commit
29bad44c52
@ -80,7 +80,7 @@
|
|||||||
<span v-if="level > 0">{{ entry.path.split('/').slice(-1 * (level + 1)).join('/') }}:</span>
|
<span v-if="level > 0">{{ entry.path.split('/').slice(-1 * (level + 1)).join('/') }}:</span>
|
||||||
<span v-else>{{ entry.name }}:</span>
|
<span v-else>{{ entry.name }}:</span>
|
||||||
{{ entry.mode.toString(8) }}, {{ entry.owner }}:{{ entry.group }}, {{ entry.sizeHuman }},
|
{{ entry.mode.toString(8) }}, {{ entry.owner }}:{{ entry.group }}, {{ entry.sizeHuman }},
|
||||||
modified: {{ entry.mtime }}
|
modified: {{ entry.mtimeStr }}
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
@ -121,10 +121,8 @@ export default {
|
|||||||
const doubleClickCallback = () => {
|
const doubleClickCallback = () => {
|
||||||
if (props.entry.resolvedType === 'd') {
|
if (props.entry.resolvedType === 'd') {
|
||||||
emit('browserAction', 'cd', props.entry);
|
emit('browserAction', 'cd', props.entry);
|
||||||
} else if (props.entry.resolvedType === 'f') {
|
|
||||||
emit('browserAction', 'openFilePrompt', props.entry);
|
|
||||||
} else {
|
} else {
|
||||||
notifications.value.constructNotification('Cannot open or download file', `${props.entry.nameHTML} is a ${props.entry.resolvedTypeHuman}`, 'denied');
|
emit('browserAction', 'openFilePrompt', props.entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
98
navigator/src/components/FileModeMatrix.vue
Normal file
98
navigator/src/components/FileModeMatrix.vue
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<!--
|
||||||
|
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
|
||||||
|
|
||||||
|
This file is part of Cockpit File Sharing.
|
||||||
|
|
||||||
|
Cockpit File Sharing is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
of the GNU General Public License as published by the Free Software Foundation, either version 3
|
||||||
|
of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
Cockpit File Sharing is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with Cockpit File Sharing.
|
||||||
|
If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="inline-grid grid-cols-[2fr_1fr_1fr_1fr] gap-2 justify-items-center">
|
||||||
|
<label class="justify-self-start block text-sm font-medium"></label>
|
||||||
|
<label class="text-label">Read</label>
|
||||||
|
<label class="text-label">Write</label>
|
||||||
|
<label class="text-label">Execute</label>
|
||||||
|
|
||||||
|
<label class="justify-self-start text-label">Owner</label>
|
||||||
|
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.owner.read" />
|
||||||
|
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.owner.write" />
|
||||||
|
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.owner.execute" />
|
||||||
|
|
||||||
|
<label class="justify-self-start text-label">Group</label>
|
||||||
|
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.group.read" />
|
||||||
|
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.group.write" />
|
||||||
|
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.group.execute" />
|
||||||
|
|
||||||
|
<label class="justify-self-start text-label">Other</label>
|
||||||
|
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.other.read" />
|
||||||
|
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.other.write" />
|
||||||
|
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.other.execute" />
|
||||||
|
|
||||||
|
<label class="justify-self-start text-label">Mode</label>
|
||||||
|
<span class="col-span-3 font-mono font-medium whitespace-nowrap">{{ modeStr }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { reactive, computed } from 'vue';
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
modelValue: Number,
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const modeStr = computed(
|
||||||
|
() =>
|
||||||
|
(props.modelValue & 0b100000000 ? 'r' : '-')
|
||||||
|
+ (props.modelValue & 0b010000000 ? 'w' : '-')
|
||||||
|
+ (props.modelValue & 0b001000000 ? 'x' : '-')
|
||||||
|
+ (props.modelValue & 0b000100000 ? 'r' : '-')
|
||||||
|
+ (props.modelValue & 0b000010000 ? 'w' : '-')
|
||||||
|
+ (props.modelValue & 0b000001000 ? 'x' : '-')
|
||||||
|
+ (props.modelValue & 0b000000100 ? 'r' : '-')
|
||||||
|
+ (props.modelValue & 0b000000010 ? 'w' : '-')
|
||||||
|
+ (props.modelValue & 0b000000001 ? 'x' : '-')
|
||||||
|
+ ` (${props.modelValue.toString(8).padStart(3, '0')})`
|
||||||
|
);
|
||||||
|
|
||||||
|
const computedBit = (mask, getter) => computed({
|
||||||
|
get: () => getter() & mask ? true : false,
|
||||||
|
set: (value) => emit('update:modelValue', value ? (props.modelValue | mask) : (props.modelValue & (~mask))),
|
||||||
|
})
|
||||||
|
|
||||||
|
const modeMatrix = reactive({
|
||||||
|
other: {
|
||||||
|
execute: computedBit(0b000000001, () => props.modelValue),
|
||||||
|
write: computedBit(0b000000010, () => props.modelValue),
|
||||||
|
read: computedBit(0b000000100, () => props.modelValue),
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
execute: computedBit(0b000001000, () => props.modelValue),
|
||||||
|
write: computedBit(0b000010000, () => props.modelValue),
|
||||||
|
read: computedBit(0b000100000, () => props.modelValue),
|
||||||
|
},
|
||||||
|
owner: {
|
||||||
|
execute: computedBit(0b001000000, () => props.modelValue),
|
||||||
|
write: computedBit(0b010000000, () => props.modelValue),
|
||||||
|
read: computedBit(0b100000000, () => props.modelValue),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
modeMatrix,
|
||||||
|
modeStr,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: [
|
||||||
|
'update:modelValue',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
</script>
|
128
navigator/src/components/FilePermissions.vue
Normal file
128
navigator/src/components/FilePermissions.vue
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<!--
|
||||||
|
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
|
||||||
|
|
||||||
|
This file is part of Cockpit File Sharing.
|
||||||
|
|
||||||
|
Cockpit File Sharing is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
of the GNU General Public License as published by the Free Software Foundation, either version 3
|
||||||
|
of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
Cockpit File Sharing is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with Cockpit File Sharing.
|
||||||
|
If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ModalPopup :showModal="show" :headerText="entry?.nameHTML" @apply="apply"
|
||||||
|
@cancel="$emit('hide')">
|
||||||
|
<div class="flex flex-col space-y-content items-start">
|
||||||
|
<FileModeMatrix v-model="mode" />
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium">Owner</label>
|
||||||
|
<select class="input-textlike" v-model="owner">
|
||||||
|
<option v-for="user in users" :value="user.user">{{ user.pretty }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium">Group</label>
|
||||||
|
<select class="input-textlike" v-model="group">
|
||||||
|
<option v-for="group in groups" :value="group.group">{{ group.pretty }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalPopup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { watch, ref, onMounted } from 'vue';
|
||||||
|
import ModalPopup from "./ModalPopup.vue";
|
||||||
|
import FileModeMatrix from "./FileModeMatrix.vue";
|
||||||
|
import { useSpawn, errorString, canonicalPath } from "@45drives/cockpit-helpers";
|
||||||
|
import { getUsers } from '../functions/getUsers';
|
||||||
|
import { getGroups } from '../functions/getGroups';
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
show: Boolean,
|
||||||
|
entry: Object,
|
||||||
|
onError: {
|
||||||
|
type: Function,
|
||||||
|
required: false,
|
||||||
|
default: console.error,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const mode = ref(0);
|
||||||
|
const owner = ref("");
|
||||||
|
const group = ref("");
|
||||||
|
const users = ref([]);
|
||||||
|
const groups = ref([]);
|
||||||
|
|
||||||
|
const getPermissions = async () => {
|
||||||
|
try {
|
||||||
|
let modeStr;
|
||||||
|
[modeStr, owner.value, group.value] = (
|
||||||
|
await useSpawn(['stat', '--format=%a:%U:%G', props.entry.path], { superuser: 'try', host: props.entry.host }).promise()
|
||||||
|
).stdout.trim().split(':');
|
||||||
|
mode.value = parseInt(modeStr, 8);
|
||||||
|
} catch (state) {
|
||||||
|
const error = new Error(errorString(state));
|
||||||
|
error.name = "Permissions Query Error";
|
||||||
|
props.onError(error);
|
||||||
|
emit('hide');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const apply = async () => {
|
||||||
|
if (canonicalPath(props.entry.path) === '/') {
|
||||||
|
const error = new Error("Cannot Edit Permissions for '/'. If you think you need to do this, you don't.");
|
||||||
|
error.name = "Permissions Apply Error";
|
||||||
|
props.onError(error);
|
||||||
|
emit('hide');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const procs = [];
|
||||||
|
procs.push(useSpawn(['chown', owner.value, props.entry.path], { superuser: 'try', host: props.entry.host }).promise());
|
||||||
|
procs.push(useSpawn(['chgrp', group.value, props.entry.path], { superuser: 'try', host: props.entry.host }).promise());
|
||||||
|
procs.push(useSpawn(['chmod', mode.value.toString(8), props.entry.path], { superuser: 'try', host: props.entry.host }).promise());
|
||||||
|
for (const proc of procs) {
|
||||||
|
try {
|
||||||
|
await proc;
|
||||||
|
} catch (state) {
|
||||||
|
const error = new Error(errorString(state));
|
||||||
|
error.name = "Permissions Apply Error";
|
||||||
|
props.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit('hide');
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
users.value = await getUsers();
|
||||||
|
groups.value = await getGroups();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.show, (show, lastShow) => {
|
||||||
|
if (show && props.entry) getPermissions();
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode,
|
||||||
|
owner,
|
||||||
|
group,
|
||||||
|
users,
|
||||||
|
groups,
|
||||||
|
apply,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ModalPopup,
|
||||||
|
FileModeMatrix,
|
||||||
|
},
|
||||||
|
emits: [
|
||||||
|
'hide',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
24
navigator/src/functions/getGroups.js
Normal file
24
navigator/src/functions/getGroups.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { useSpawn } from "@45drives/cockpit-helpers";
|
||||||
|
|
||||||
|
export async function getGroups() {
|
||||||
|
let groups = [];
|
||||||
|
const groupDB = (await useSpawn(['getent', 'group'], { superuser: 'try' }).promise()).stdout;
|
||||||
|
groupDB.split('\n').forEach((record) => {
|
||||||
|
const fields = record.split(':');
|
||||||
|
const group = fields[0];
|
||||||
|
const gid = fields[2];
|
||||||
|
if (gid >= 1000 || gid === '0')
|
||||||
|
groups.push({ group: group, domain: false, pretty: group });
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
await useSpawn(['realm', 'list'], { superuser: 'try' }).promise(); // throws if not domain
|
||||||
|
const domainGroupsDB = (await useSpawn(['wbinfo', '-g'], { superuser: 'try' }).promise()).stdout
|
||||||
|
domainGroupsDB.split('\n').forEach((record) => {
|
||||||
|
if (/^\s*$/.test(record))
|
||||||
|
return;
|
||||||
|
groups.push({ group: record.replace(/^[^\\]+\\/, ""), domain: true, pretty: record.replace(/^[^\\]+\\/, "") + " (domain)" });
|
||||||
|
})
|
||||||
|
} catch {}
|
||||||
|
groups.sort((a, b) => a.pretty.localeCompare(b.pretty));
|
||||||
|
return groups
|
||||||
|
}
|
24
navigator/src/functions/getUsers.js
Normal file
24
navigator/src/functions/getUsers.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { useSpawn } from "@45drives/cockpit-helpers";
|
||||||
|
|
||||||
|
export async function getUsers() {
|
||||||
|
let users = [];
|
||||||
|
const passwdDB = (await useSpawn(['getent', 'passwd'], { superuser: 'try' }).promise()).stdout;
|
||||||
|
passwdDB.split('\n').forEach((record) => {
|
||||||
|
const fields = record.split(':');
|
||||||
|
const user = fields[0];
|
||||||
|
const uid = fields[2];
|
||||||
|
if (uid >= 1000 || uid === '0') // include root
|
||||||
|
users.push({ user: user, domain: false, pretty: user });
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
await useSpawn(['realm', 'list'], { superuser: 'try' }).promise(); // throws if not domain
|
||||||
|
const domainUsersDB = (await useSpawn(['wbinfo', '-u'], { superuser: 'try' }).promise()).stdout;
|
||||||
|
domainUsersDB.split('\n').forEach((record) => {
|
||||||
|
if (/^\s*$/.test(record))
|
||||||
|
return;
|
||||||
|
users.push({ user: record.replace(/^[^\\]+\\/, ""), domain: true, pretty: record.replace(/^[^\\]+\\/, "") + " (domain)" });
|
||||||
|
})
|
||||||
|
} catch {}
|
||||||
|
users.sort((a, b) => a.pretty.localeCompare(b.pretty));
|
||||||
|
return users;
|
||||||
|
}
|
@ -68,25 +68,31 @@
|
|||||||
<div class="grow overflow-hidden">
|
<div class="grow overflow-hidden">
|
||||||
<DirectoryView :host="pathHistory.current()?.host" :path="pathHistory.current()?.path"
|
<DirectoryView :host="pathHistory.current()?.host" :path="pathHistory.current()?.path"
|
||||||
:searchFilterRegExp="searchFilterRegExp" @cd="path => cd({ path })" @edit="openEditor"
|
:searchFilterRegExp="searchFilterRegExp" @cd="path => cd({ path })" @edit="openEditor"
|
||||||
@browserAction="handleAction"
|
@browserAction="handleAction" ref="directoryViewRef" />
|
||||||
ref="directoryViewRef" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ModalPopup :showModal="openFilePromptModal.show" :headerText="openFilePromptModal.entry?.name ?? 'NULL'" @close="() => openFilePromptModal.close()">
|
<ModalPopup :showModal="openFilePromptModal.show" :headerText="openFilePromptModal.entry?.name ?? 'NULL'"
|
||||||
What would you like to do with this file?
|
@close="() => openFilePromptModal.close()" autoWidth>
|
||||||
|
What would you like to do with this {{ openFilePromptModal.entry?.resolvedTypeHuman }}?
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<button type="button" class="btn btn-secondary" @click="() => openFilePromptModal.close()">
|
<button type="button" class="btn btn-secondary" @click="() => openFilePromptModal.close()">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-primary" @click="() => openFilePromptModal.action('edit') ">
|
<button type="button" class="btn btn-primary" @click="() => openFilePromptModal.action('editPermissions')">
|
||||||
|
Edit permissions
|
||||||
|
</button>
|
||||||
|
<button v-if="openFilePromptModal.entry?.resolvedType === 'f'" type="button" class="btn btn-primary"
|
||||||
|
@click="() => openFilePromptModal.action('edit')">
|
||||||
Open for editing
|
Open for editing
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-primary" @click="() => openFilePromptModal.action('download') ">
|
<button v-if="openFilePromptModal.entry?.resolvedType === 'f'" type="button" class="btn btn-primary"
|
||||||
|
@click="() => openFilePromptModal.action('download')">
|
||||||
Download
|
Download
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</ModalPopup>
|
</ModalPopup>
|
||||||
|
<FilePermissions :show="filePermissions.show" @hide="filePermissions.close" :entry="filePermissions.entry" />
|
||||||
<Teleport to="#footer-buttons">
|
<Teleport to="#footer-buttons">
|
||||||
<IconToggle v-model="darkMode" v-slot="{ value }">
|
<IconToggle v-model="darkMode" v-slot="{ value }">
|
||||||
<MoonIcon v-if="value" class="size-icon icon-default" />
|
<MoonIcon v-if="value" class="size-icon icon-default" />
|
||||||
@ -113,6 +119,7 @@ import { ArrowLeftIcon, ArrowRightIcon, ArrowUpIcon, RefreshIcon, ChevronDownIco
|
|||||||
import IconToggle from '../components/IconToggle.vue';
|
import IconToggle from '../components/IconToggle.vue';
|
||||||
import ModalPopup from '../components/ModalPopup.vue';
|
import ModalPopup from '../components/ModalPopup.vue';
|
||||||
import { fileDownload } from '@45drives/cockpit-helpers';
|
import { fileDownload } from '@45drives/cockpit-helpers';
|
||||||
|
import FilePermissions from '../components/FilePermissions.vue';
|
||||||
|
|
||||||
const encodePartial = (string) =>
|
const encodePartial = (string) =>
|
||||||
encodeURIComponent(string)
|
encodeURIComponent(string)
|
||||||
@ -179,6 +186,20 @@ export default {
|
|||||||
openFilePromptModal.close();
|
openFilePromptModal.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const filePermissions = reactive({
|
||||||
|
show: false,
|
||||||
|
entry: null,
|
||||||
|
resetTimeoutHandle: null,
|
||||||
|
open: (entry) => {
|
||||||
|
clearTimeout(filePermissions.resetTimeoutHandle);
|
||||||
|
filePermissions.entry = entry;
|
||||||
|
filePermissions.show = true;
|
||||||
|
},
|
||||||
|
close: () => {
|
||||||
|
filePermissions.show = false;
|
||||||
|
filePermissions.resetTimeoutHandle = setTimeout(() => filePermissions.resetTimeoutHandle = filePermissions.entry = null, 500);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const cd = ({ path, host }) => {
|
const cd = ({ path, host }) => {
|
||||||
const newHost = host ?? (pathHistory.current().host);
|
const newHost = host ?? (pathHistory.current().host);
|
||||||
@ -195,7 +216,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const up = () => {
|
const up = () => {
|
||||||
cd({path: pathHistory.current().path + '/..'});
|
cd({ path: pathHistory.current().path + '/..' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const openEditor = ({ path, host }) => {
|
const openEditor = ({ path, host }) => {
|
||||||
@ -213,6 +234,10 @@ export default {
|
|||||||
openFilePromptModal.open(entry);
|
openFilePromptModal.open(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openFilePermissions = (entry) => {
|
||||||
|
filePermissions.open(entry);
|
||||||
|
}
|
||||||
|
|
||||||
const getSelected = () => directoryViewRef.value?.getSelected?.() ?? [];
|
const getSelected = () => directoryViewRef.value?.getSelected?.() ?? [];
|
||||||
|
|
||||||
const handleAction = (action, ...args) => {
|
const handleAction = (action, ...args) => {
|
||||||
@ -223,6 +248,9 @@ export default {
|
|||||||
case 'edit':
|
case 'edit':
|
||||||
openEditor(...args);
|
openEditor(...args);
|
||||||
break;
|
break;
|
||||||
|
case 'editPermissions':
|
||||||
|
openFilePermissions(...args);
|
||||||
|
break;
|
||||||
case 'openFilePrompt':
|
case 'openFilePrompt':
|
||||||
openFilePrompt(...args);
|
openFilePrompt(...args);
|
||||||
break;
|
break;
|
||||||
@ -267,6 +295,7 @@ export default {
|
|||||||
backHistoryDropdown,
|
backHistoryDropdown,
|
||||||
forwardHistoryDropdown,
|
forwardHistoryDropdown,
|
||||||
openFilePromptModal,
|
openFilePromptModal,
|
||||||
|
filePermissions,
|
||||||
cd,
|
cd,
|
||||||
back,
|
back,
|
||||||
forward,
|
forward,
|
||||||
@ -274,6 +303,7 @@ export default {
|
|||||||
openEditor,
|
openEditor,
|
||||||
download,
|
download,
|
||||||
openFilePrompt,
|
openFilePrompt,
|
||||||
|
openFilePermissions,
|
||||||
getSelected,
|
getSelected,
|
||||||
handleAction,
|
handleAction,
|
||||||
}
|
}
|
||||||
@ -295,6 +325,7 @@ export default {
|
|||||||
ViewListIcon,
|
ViewListIcon,
|
||||||
ViewGridIcon,
|
ViewGridIcon,
|
||||||
ModalPopup,
|
ModalPopup,
|
||||||
|
FilePermissions,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user