1
1
mirror of https://github.com/pterodactyl/panel.git synced 2024-11-23 01:22:30 +01:00

Add support for moving files via the file manager

This commit is contained in:
Dane Everitt 2019-03-16 16:36:08 -07:00
parent 5aa40800c8
commit 4e669771ca
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
5 changed files with 154 additions and 10 deletions

View File

@ -1,15 +1,17 @@
import {withCredentials} from "@/api/http";
import {ServerApplicationCredentials} from "@/store/types";
type PathChangeObject = {
currentPath: string,
newPath: string,
}
/**
* Creates a copy of the given file or directory on the Daemon. Expects a fully resolved path
* to be passed through for both data arguments.
*/
export function copyElement(server: string, credentials: ServerApplicationCredentials, data: {
currentPath: string, newPath: string
}): Promise<void> {
export function copyElement(server: string, credentials: ServerApplicationCredentials, data: PathChangeObject, isMove = false): Promise<void> {
return new Promise((resolve, reject) => {
withCredentials(server, credentials).post('/v1/server/file/copy', {
withCredentials(server, credentials).post(`/v1/server/file/${isMove ? 'move' : 'copy'}`, {
from: data.currentPath,
to: data.newPath,
})
@ -17,3 +19,11 @@ export function copyElement(server: string, credentials: ServerApplicationCreden
.catch(reject);
});
}
/**
* Moves a file or folder to a new location on the server. Works almost exactly the same as the copy
* file logic, so it really just passes an extra argument to copy to indicate that it is a move.
*/
export function moveElement(server: string, credentials: ServerApplicationCredentials, data: PathChangeObject): Promise<void> {
return copyElement(server, credentials, data, true);
}

View File

@ -7,7 +7,7 @@
</div>
<div class="action"><span>Rename</span></div>
</div>
<div class="context-row">
<div class="context-row" v-on:click="triggerAction('move')">
<div class="icon">
<Icon name="corner-up-left" class="h-4"/>
</div>

View File

@ -34,11 +34,13 @@
v-on:action:delete="showModal('delete')"
v-on:action:rename="showModal('rename')"
v-on:action:copy="showModal('copy')"
v-on:action:move="showModal('move')"
ref="contextMenu"
/>
<CopyFileModal :file="file" v-if="modals.copy" v-on:close="$emit('list')"/>
<DeleteFileModal :visible.sync="modals.delete" :object="file" v-on:deleted="$emit('deleted')" v-on:close="modal.delete = false"/>
<RenameModal :visible.sync="modals.rename" :object="file" v-on:renamed="$emit('list')" v-on:close="modal.rename = false"/>
<MoveFileModal :visible.sync="modals.move" :file="file" v-on:moved="$emit('list')" v-on:close="modal.move = false"/>
</div>
</template>
@ -52,6 +54,7 @@
import DeleteFileModal from "@/components/server/components/filemanager/modals/DeleteFileModal.vue";
import RenameModal from "@/components/server/components/filemanager/modals/RenameModal.vue";
import CopyFileModal from "@/components/server/components/filemanager/modals/CopyFileModal.vue";
import MoveFileModal from "@/components/server/components/filemanager/modals/MoveFileModal.vue";
type DataStructure = {
currentDirectory: string,
@ -61,7 +64,7 @@
export default Vue.extend({
name: 'FileRow',
components: {CopyFileModal, DeleteFileModal, Icon, FileContextMenu, RenameModal},
components: {CopyFileModal, DeleteFileModal, MoveFileModal, Icon, FileContextMenu, RenameModal},
props: {
file: {
@ -83,6 +86,7 @@
rename: false,
delete: false,
copy: false,
move: false,
},
};
},

View File

@ -0,0 +1,130 @@
<template>
<Modal :show="visible" v-on:close="isVisible = false" :dismissable="!isLoading">
<MessageBox class="alert error mb-8" title="Error" :message="error" v-if="error"/>
<div class="flex items-end">
<div class="flex-1">
<label class="input-label">
Move {{ file.name}}
</label>
<input
type="text" class="input" name="move_to"
:placeholder="file.name"
ref="moveToField"
v-model="moveTo"
v-validate="{ required: true, regex: /(^[\w\d.\-\/]+$)/}"
v-on:keyup.enter="submit"
/>
</div>
<div class="ml-4">
<button type="submit"
class="btn btn-primary btn-sm"
v-on:click.prevent="submit"
:disabled="errors.any() || isLoading"
>
<span class="spinner white" v-bind:class="{ hidden: !isLoading }">&nbsp;</span>
<span :class="{ hidden: isLoading }">
Move {{ file.directory ? 'Folder' : 'File' }}
</span>
</button>
</div>
</div>
<p class="input-help error" v-if="errors.count()">
{{ errors.first('move_to') }}
</p>
<p class="input-help" v-else>
Enter the new name and path for this {{ file.directory ? 'folder' : 'file' }} in the field above. This will be relative to the current directory.
</p>
</Modal>
</template>
<script lang="ts">
import Vue from 'vue';
import Modal from "@/components/core/Modal.vue";
import MessageBox from "@/components/MessageBox.vue";
import {DirectoryContentObject} from "@/api/server/types";
import {moveElement} from '@/api/server/files/copyElement';
import {mapState} from "vuex";
import {ApplicationState} from "@/store/types";
import {join} from 'path';
import {AxiosError} from "axios";
type DataStructure = {
error: null | string,
isLoading: boolean,
moveTo: null | string,
};
export default Vue.extend({
name: 'MoveFileModal',
components: { MessageBox, Modal },
data: function (): DataStructure {
return {
error: null,
isLoading: false,
moveTo: null,
};
},
props: {
visible: { type: Boolean, default: false },
file: { type: Object as () => DirectoryContentObject, required: true }
},
computed: {
...mapState({
server: (state: ApplicationState) => state.server.server,
credentials: (state: ApplicationState) => state.server.credentials,
fm: (state: ApplicationState) => state.server.fm,
}),
isVisible: {
get: function (): boolean {
return this.visible;
},
set: function (value: boolean) {
this.$emit('update:visible', value)
},
}
},
watch: {
isVisible: function (n, o): void {
if (n !== o) {
this.resetModal();
}
if (n && !o) {
this.$nextTick(() => (this.$refs.moveToField as HTMLElement).focus());
}
},
},
methods: {
submit: function () {
this.isLoading = true;
// @ts-ignore
moveElement(this.server.uuid, this.credentials, {
// @ts-ignore
currentPath: join(this.fm.currentDirectory, this.file.name),
// @ts-ignore
newPath: join(this.fm.currentDirectory, this.moveTo),
})
.then(() => this.$emit('moved'))
.catch((error: AxiosError) => {
this.error = `There was an error moving the requested ${(this.file.directory) ? 'folder' : 'file'}. Response was: ${error.message}`;
console.error('Error at Server::Files::Move', {error});
})
.then(() => this.isLoading = false);
},
resetModal: function () {
this.isLoading = false;
this.moveTo = null;
this.error = null;
},
}
});
</script>

View File

@ -16,8 +16,8 @@
:placeholder="object.name"
ref="elementNameField"
v-model="newName"
v-validate.disabled="'required'"
v-validate="'alpha_dash'"
:data-vv-as="object.directory ? 'folder name' : 'file name'"
v-validate="{ required: true, regex: /(^[\w\d.\-\/]+$)/}"
v-on:keyup.enter="submit"
/>
</div>
@ -34,8 +34,8 @@
</button>
</div>
</div>
<p class="input-help error">
{{ errors.first('folder_name') }}
<p class="input-help error" v-if="errors.count()">
{{ errors.first('element_name') }}
</p>
</Modal>
</template>