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:
parent
5aa40800c8
commit
4e669771ca
@ -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);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -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 }"> </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>
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user