feat: add moonraker file_manager permissions to store and config files

Signed-off-by: Stefan Dej <meteyou@gmail.com>
This commit is contained in:
Stefan Dej
2021-10-30 23:12:08 +02:00
parent 931d0a6d50
commit 8ed9f33dc0
12 changed files with 98 additions and 72 deletions

View File

@@ -122,8 +122,12 @@ export default class TheEditor extends Mixins(BaseMixin) {
return this.$store.state.editor.fileroot ?? 'gcodes'
}
get permissions(): string {
return this.$store.state.editor.permissions ?? 'r'
}
get isWriteable() {
return ['config', 'gcodes'].includes(this.fileroot)
return this.permissions.includes('w')
}
get sourcecode() {

View File

@@ -136,21 +136,21 @@
<v-menu v-model="contextMenu.shown" :position-x="contextMenu.x" :position-y="contextMenu.y" absolute offset-y>
<v-list>
<v-list-item @click="clickRow(contextMenu.item, true)" v-if="!contextMenu.item.isDirectory">
<v-icon class="mr-1">mdi-file-document-edit-outline</v-icon> {{ isDirWriteable ? $t('Machine.ConfigFilesPanel.EditFile') : $t('Machine.ConfigFilesPanel.ShowFile') }}
<v-icon class="mr-1">mdi-file-document-edit-outline</v-icon> {{ contextMenu.item.permissions.includes('w') ? $t('Machine.ConfigFilesPanel.EditFile') : $t('Machine.ConfigFilesPanel.ShowFile') }}
</v-list-item>
<v-list-item @click="downloadFile" v-if="!contextMenu.item.isDirectory">
<v-icon class="mr-1">mdi-cloud-download</v-icon> {{ $t('Machine.ConfigFilesPanel.Download') }}
</v-list-item>
<v-list-item @click="renameFile(contextMenu.item)" v-if="!contextMenu.item.isDirectory && isDirWriteable">
<v-list-item @click="renameFile(contextMenu.item)" v-if="!contextMenu.item.isDirectory && contextMenu.item.permissions.includes('w')">
<v-icon class="mr-1">mdi-rename-box</v-icon> {{ $t('Machine.ConfigFilesPanel.Rename') }}
</v-list-item>
<v-list-item @click="renameDirectory(contextMenu.item)" v-if="contextMenu.item.isDirectory && isDirWriteable">
<v-list-item @click="renameDirectory(contextMenu.item)" v-if="contextMenu.item.isDirectory && contextMenu.item.permissions.includes('w')">
<v-icon class="mr-1">mdi-rename-box</v-icon> {{ $t('Machine.ConfigFilesPanel.Rename') }}
</v-list-item>
<v-list-item @click="removeFile" v-if="!contextMenu.item.isDirectory && isDirWriteable">
<v-list-item @click="removeFile" v-if="!contextMenu.item.isDirectory && contextMenu.item.permissions.includes('w')">
<v-icon class="mr-1">mdi-delete</v-icon> {{ $t('Machine.ConfigFilesPanel.Delete') }}
</v-list-item>
<v-list-item @click="deleteDirectory(contextMenu.item)" v-if="contextMenu.item.isDirectory && isDirWriteable">
<v-list-item @click="deleteDirectory(contextMenu.item)" v-if="contextMenu.item.isDirectory && contextMenu.item.permissions.includes('w')">
<v-icon class="mr-1">mdi-delete</v-icon> {{ $t('Machine.ConfigFilesPanel.Delete') }}
</v-list-item>
</v-list>
@@ -249,10 +249,9 @@
<script lang="ts">
import {Component, Mixins, Watch} from 'vue-property-decorator'
import {Component, Mixins} from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import {readOnlyRoots} from '@/store/variables'
import {findDirectory, formatDate, formatFilesize, sortFiles} from '@/plugins/helpers'
import {formatDate, formatFilesize, sortFiles} from '@/plugins/helpers'
import {FileStateFile} from '@/store/files/types'
import axios from 'axios'
import Panel from '@/components/ui/Panel.vue'
@@ -322,7 +321,6 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
private selected = []
private options = { }
private currentPage = 1
private files: FileStateFile[] = []
private currentPath = ''
private root = 'config'
private contextMenu: contextMenu = {
@@ -334,6 +332,7 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
item: {
isDirectory: false,
filename: '',
permissions: '',
modified: new Date()
}
}
@@ -351,6 +350,7 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
item: {
isDirectory: false,
filename: '',
permissions: '',
modified: new Date()
}
}
@@ -368,6 +368,7 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
item: {
isDirectory: false,
filename: '',
permissions: '',
modified: new Date()
}
}
@@ -376,6 +377,7 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
item: {
isDirectory: false,
filename: '',
permissions: '',
modified: new Date()
}
}
@@ -398,6 +400,7 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
item: {
isDirectory: false,
filename: '',
permissions: '',
modified: new Date()
}
}
@@ -438,12 +441,42 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
get filteredToolbarButtons() {
return this.toolbarButtons.filter((button) => {
return (this.isDirWriteable && button.onlyWriteable) || !button.onlyWriteable
return (this.directoryPermissions.includes('w') && button.onlyWriteable) || !button.onlyWriteable
})
}
get filetree() {
return this.$store.state.files.filetree
get absolutePath() {
let path = '/'+this.root
if (this.currentPath) path += this.currentPath
return path
}
get directory() {
return this.$store.getters['files/getDirectory'](this.absolutePath)
}
get disk_usage() {
return this.directory?.disk_usage ?? { used: 0, free: 0, total: 0}
}
get directoryPermissions() {
return this.directory?.permissions ?? 'r'
}
get files() {
let files = [...this.directory?.childrens ?? []]
if (!this.showHiddenFiles) {
files = files.filter(file => file.filename.substr(0, 1) !== '.')
}
if (this.hideBackupFiles) {
const backupFileMatcher = /.*\/?printer-\d{8}_\d{6}\.cfg$/
files = files.filter(file => !file.filename.match(backupFileMatcher))
}
return files
}
get headers() {
@@ -503,46 +536,12 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
return this.$store.state.server.registered_directories.filter((dir: string) => dir !== 'gcodes').sort()
}
get availableServices() {
return this.$store.state.server.system_info.available_services
}
get absolutePath() {
let path = '/'+this.root
if (this.currentPath) path += this.currentPath
return path
}
get disk_usage() {
return this.$store.getters['files/getDiskUsage'](this.absolutePath)
}
get isDirWriteable() {
return !readOnlyRoots.includes(this.root)
}
refreshFileList() {
this.$socket.emit('server.files.get_directory', { path: this.absolutePath.substring(1) }, { action: 'files/getDirectory' })
}
changeRoot() {
this.currentPath = ''
this.loadPath()
}
loadPath() {
let dirArray = this.absolutePath.substring(1).split('/')
this.files = findDirectory(this.filetree, dirArray) ?? []
if (!this.showHiddenFiles) {
this.files = this.files.filter(file => file.filename.substr(0, 1) !== '.')
}
if (this.hideBackupFiles) {
const backupFileMatcher = /.*\/?printer-\d{8}_\d{6}\.cfg$/
this.files = this.files.filter(file => !file.filename.match(backupFileMatcher))
}
}
clickRow(item: FileStateFile, force = false) {
@@ -565,10 +564,12 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
this.dialogImage.item.url = url
}
} else {
window.console.log('openFile', item)
this.$store.dispatch('editor/openFile', {
root: this.root,
path: this.currentPath,
filename: item.filename
filename: item.filename,
permissions: item.permissions
})
}
} else {
@@ -811,19 +812,5 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
}, { action: 'files/getMove' })
}
}
created() { this.loadPath() }
@Watch('filetree', { deep: true })
filetreeChanged() { this.loadPath() }
@Watch('currentPath')
currentPathChanged() { this.loadPath() }
@Watch('showHiddenFiles')
showHiddenFilesChanged() { this.loadPath() }
@Watch('hideBackupFiles')
hideBackupFilesChanged() { this.loadPath() }
}
</script>

View File

@@ -1,4 +1,3 @@
import { ServerStateEvent } from '@/store/server/types'
import {FileStateFile} from '@/store/files/types'
import {PrinterStateMacroParams} from '@/store/printer/types'

View File

@@ -23,6 +23,7 @@ export const actions: ActionTree<EditorState, RootState> = {
commit('updateLoaderState', true)
commit('setFilename', payload.filename)
commit('setPermissions', payload.permissions)
axios.get(url, {
cancelToken: source.token,

View File

@@ -8,6 +8,7 @@ export const getDefaultState = (): EditorState => {
return {
bool: false,
filename: '',
permissions: '',
fileroot: '',
filepath: '',
sourcecode: '',

View File

@@ -45,6 +45,10 @@ export const mutations: MutationTree<EditorState> = {
Vue.set(state, 'filename', filename)
},
setPermissions(state, filename) {
Vue.set(state, 'permissions', filename)
},
hideEditor(state) {
Vue.set(state, 'bool', false)
},
@@ -62,11 +66,8 @@ export const mutations: MutationTree<EditorState> = {
// the hash took 2 seconds per run, the editor itself is pretty laggy even without hash
// calculations. Hash calculations with typical config file sizes (50KB) only take 1 or 2ms
// on my machine, so I guess this is acceptable for most use cases.
if (sha256(payload) != state.loadedHash)
state.changed = true
else
state.changed = false
state.changed = (sha256(payload) != state.loadedHash)
}
}

View File

@@ -2,6 +2,7 @@ export interface EditorState {
bool: boolean
filename: string
fileroot: string
permissions: string
filepath: string
sourcecode: string
loaderBool: boolean

View File

@@ -31,7 +31,8 @@ export const getDefaultState = (): FarmPrinterState => {
current_file: {
isDirectory: false,
filename: '',
modified: new Date()
modified: new Date(),
permissions: ''
},
theme_files: []
}

View File

@@ -10,6 +10,7 @@ import {
import {findDirectory} from '@/plugins/helpers'
import {RootState} from '@/store/types'
import i18n from '@/plugins/i18n'
import {readOnlyRoots} from '@/store/variables'
export const actions: ActionTree<FileState, RootState> = {
reset({ commit }) {
@@ -19,7 +20,10 @@ export const actions: ActionTree<FileState, RootState> = {
initRootDirs({ state, commit }, dirs) {
dirs.forEach((dirname: string) => {
if (state.filetree.findIndex((tmp: FileStateFile) => tmp.filename === dirname) === -1) {
commit('createRootDir', dirname)
commit('createRootDir', {
name: dirname,
permissions: readOnlyRoots.includes(dirname) ? 'r' : 'rw'
})
Vue.$socket.emit('server.files.get_directory', { path: dirname }, { action: 'files/getDirectory' })
}
})
@@ -65,6 +69,7 @@ export const actions: ActionTree<FileState, RootState> = {
item: {
path: path.length ? path+'/'+dir.dirname : dir.dirname,
root: root,
permissions: dir.permissions,
modified: dir.modified * 1000
}
})
@@ -95,6 +100,7 @@ export const actions: ActionTree<FileState, RootState> = {
item: {
path: path.length ? path+'/'+file.filename : file.filename,
root: root,
permissions: file.permissions,
modified: file.modified,
size: file.size,
}

View File

@@ -6,6 +6,25 @@ import {FileState, FileStateFile} from '@/store/files/types'
// eslint-disable-next-line
export const getters: GetterTree<FileState, any> = {
getDirectory: (state) => (requestedPath) => {
if (requestedPath.startsWith('/')) requestedPath = requestedPath.substr(1)
const findDirectory = function(filetree: FileStateFile[], pathArray: string[]): FileStateFile | null {
if (pathArray.length) {
const newFiletree = filetree?.childrens?.find((element: FileStateFile) => (element.isDirectory && element.filename === pathArray[0]))
if (newFiletree) {
pathArray.shift()
return findDirectory(newFiletree, pathArray)
} else return null
}
return filetree
}
return findDirectory({ childrens: state.filetree }, requestedPath.split('/'))
},
getThemeFileUrl: (state, getters, rootState, rootGetters) => (acceptName: string, acceptExtensions: string[]) => {
const directory = findDirectory(state.filetree, ['config', themeDir])

View File

@@ -13,11 +13,12 @@ export const mutations: MutationTree<FileState> = {
Object.assign(state, getDefaultState())
},
createRootDir(state, dirname) {
createRootDir(state, payload) {
state.filetree.push({
isDirectory: true,
filename: dirname,
filename: payload.name,
modified: new Date(),
permissions: payload.permissions,
childrens: [],
disk_usage: {
free: 0,
@@ -62,6 +63,7 @@ export const mutations: MutationTree<FileState> = {
isDirectory: false,
filename: filename,
modified: modified,
permissions: payload.item.permissions,
size: payload.item.size,
metadataPulled: false,
})
@@ -199,6 +201,7 @@ export const mutations: MutationTree<FileState> = {
isDirectory: true,
filename: dirname,
modified: payload.item.modified ?? new Date(),
permissions: payload.item.permissions,
childrens: [],
})
}

View File

@@ -6,6 +6,7 @@ export interface FileStateFile {
isDirectory: boolean
filename: string
modified: Date
permissions: string
childrens?: FileStateFile[]
disk_usage?: FileStateDiskUsage
print_start_time?: Date | null
@@ -52,10 +53,12 @@ export interface ApiGetDirectoryReturnDir {
modified: number
size: number
dirname: string
permissions: string
}
export interface ApiGetDirectoryReturnFile {
modified: number
size: number
filename: string
permissions: string
}