feat: add moonraker file_manager permissions to store and config files
Signed-off-by: Stefan Dej <meteyou@gmail.com>
This commit is contained in:
@@ -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() {
|
||||
|
@@ -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>
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { ServerStateEvent } from '@/store/server/types'
|
||||
import {FileStateFile} from '@/store/files/types'
|
||||
import {PrinterStateMacroParams} from '@/store/printer/types'
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -8,6 +8,7 @@ export const getDefaultState = (): EditorState => {
|
||||
return {
|
||||
bool: false,
|
||||
filename: '',
|
||||
permissions: '',
|
||||
fileroot: '',
|
||||
filepath: '',
|
||||
sourcecode: '',
|
||||
|
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ export interface EditorState {
|
||||
bool: boolean
|
||||
filename: string
|
||||
fileroot: string
|
||||
permissions: string
|
||||
filepath: string
|
||||
sourcecode: string
|
||||
loaderBool: boolean
|
||||
|
@@ -31,7 +31,8 @@ export const getDefaultState = (): FarmPrinterState => {
|
||||
current_file: {
|
||||
isDirectory: false,
|
||||
filename: '',
|
||||
modified: new Date()
|
||||
modified: new Date(),
|
||||
permissions: ''
|
||||
},
|
||||
theme_files: []
|
||||
}
|
||||
|
@@ -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,
|
||||
}
|
||||
|
@@ -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])
|
||||
|
||||
|
@@ -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: [],
|
||||
})
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
Reference in New Issue
Block a user