From 29bad44c52fdfefb2b189e146e02b79b5bdac5fe Mon Sep 17 00:00:00 2001 From: joshuaboud Date: Fri, 17 Jun 2022 15:46:51 -0300 Subject: [PATCH] add editing file permissions --- navigator/src/components/DirectoryEntry.vue | 6 +- navigator/src/components/FileModeMatrix.vue | 98 ++++++++++++++ navigator/src/components/FilePermissions.vue | 128 +++++++++++++++++++ navigator/src/functions/getGroups.js | 24 ++++ navigator/src/functions/getUsers.js | 24 ++++ navigator/src/views/Browser.vue | 45 ++++++- 6 files changed, 314 insertions(+), 11 deletions(-) create mode 100644 navigator/src/components/FileModeMatrix.vue create mode 100644 navigator/src/components/FilePermissions.vue create mode 100644 navigator/src/functions/getGroups.js create mode 100644 navigator/src/functions/getUsers.js diff --git a/navigator/src/components/DirectoryEntry.vue b/navigator/src/components/DirectoryEntry.vue index 01edff3..270a62a 100644 --- a/navigator/src/components/DirectoryEntry.vue +++ b/navigator/src/components/DirectoryEntry.vue @@ -80,7 +80,7 @@ {{ entry.path.split('/').slice(-1 * (level + 1)).join('/') }}: {{ entry.name }}: {{ entry.mode.toString(8) }}, {{ entry.owner }}:{{ entry.group }}, {{ entry.sizeHuman }}, - modified: {{ entry.mtime }} + modified: {{ entry.mtimeStr }} @@ -121,10 +121,8 @@ export default { const doubleClickCallback = () => { if (props.entry.resolvedType === 'd') { emit('browserAction', 'cd', props.entry); - } else if (props.entry.resolvedType === 'f') { - emit('browserAction', 'openFilePrompt', props.entry); } else { - notifications.value.constructNotification('Cannot open or download file', `${props.entry.nameHTML} is a ${props.entry.resolvedTypeHuman}`, 'denied'); + emit('browserAction', 'openFilePrompt', props.entry); } } diff --git a/navigator/src/components/FileModeMatrix.vue b/navigator/src/components/FileModeMatrix.vue new file mode 100644 index 0000000..91577ae --- /dev/null +++ b/navigator/src/components/FileModeMatrix.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/navigator/src/components/FilePermissions.vue b/navigator/src/components/FilePermissions.vue new file mode 100644 index 0000000..200906e --- /dev/null +++ b/navigator/src/components/FilePermissions.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/navigator/src/functions/getGroups.js b/navigator/src/functions/getGroups.js new file mode 100644 index 0000000..750acae --- /dev/null +++ b/navigator/src/functions/getGroups.js @@ -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 +} diff --git a/navigator/src/functions/getUsers.js b/navigator/src/functions/getUsers.js new file mode 100644 index 0000000..00edcb6 --- /dev/null +++ b/navigator/src/functions/getUsers.js @@ -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; +} diff --git a/navigator/src/views/Browser.vue b/navigator/src/views/Browser.vue index 9c5ade2..70b9720 100644 --- a/navigator/src/views/Browser.vue +++ b/navigator/src/views/Browser.vue @@ -68,25 +68,31 @@
+ @browserAction="handleAction" ref="directoryViewRef" />
- - What would you like to do with this file? + + What would you like to do with this {{ openFilePromptModal.entry?.resolvedTypeHuman }}? + @@ -113,6 +119,7 @@ import { ArrowLeftIcon, ArrowRightIcon, ArrowUpIcon, RefreshIcon, ChevronDownIco import IconToggle from '../components/IconToggle.vue'; import ModalPopup from '../components/ModalPopup.vue'; import { fileDownload } from '@45drives/cockpit-helpers'; +import FilePermissions from '../components/FilePermissions.vue'; const encodePartial = (string) => encodeURIComponent(string) @@ -179,6 +186,20 @@ export default { 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 newHost = host ?? (pathHistory.current().host); @@ -195,7 +216,7 @@ export default { } const up = () => { - cd({path: pathHistory.current().path + '/..'}); + cd({ path: pathHistory.current().path + '/..' }); } const openEditor = ({ path, host }) => { @@ -213,6 +234,10 @@ export default { openFilePromptModal.open(entry); } + const openFilePermissions = (entry) => { + filePermissions.open(entry); + } + const getSelected = () => directoryViewRef.value?.getSelected?.() ?? []; const handleAction = (action, ...args) => { @@ -223,6 +248,9 @@ export default { case 'edit': openEditor(...args); break; + case 'editPermissions': + openFilePermissions(...args); + break; case 'openFilePrompt': openFilePrompt(...args); break; @@ -267,6 +295,7 @@ export default { backHistoryDropdown, forwardHistoryDropdown, openFilePromptModal, + filePermissions, cd, back, forward, @@ -274,6 +303,7 @@ export default { openEditor, download, openFilePrompt, + openFilePermissions, getSelected, handleAction, } @@ -295,6 +325,7 @@ export default { ViewListIcon, ViewGridIcon, ModalPopup, + FilePermissions, }, }