More filemanager work, directory browsing working

This commit is contained in:
Dane Everitt 2018-08-13 22:58:58 -07:00
parent ceef2edf2e
commit e9f8751c4c
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
4 changed files with 104 additions and 37 deletions

View file

@ -39,18 +39,6 @@ class FileController extends Controller
$this->authorize('list-files', $server); $this->authorize('list-files', $server);
$requestDirectory = '/' . trim(urldecode($request->route()->parameter('directory', '/')), '/'); $requestDirectory = '/' . trim(urldecode($request->route()->parameter('directory', '/')), '/');
$directory = [
'header' => $requestDirectory !== '/' ? $requestDirectory : '',
'first' => $requestDirectory !== '/',
];
$goBack = explode('/', trim($requestDirectory, '/'));
if (! empty(array_filter($goBack)) && count($goBack) >= 2) {
array_pop($goBack);
$directory['show'] = true;
$directory['link'] = '/' . implode('/', $goBack);
$directory['link_show'] = implode('/', $goBack) . '/';
}
try { try {
$contents = $this->fileRepository->setServer($server)->setToken( $contents = $this->fileRepository->setServer($server)->setToken(
@ -63,7 +51,7 @@ class FileController extends Controller
return JsonResponse::create([ return JsonResponse::create([
'contents' => $contents, 'contents' => $contents,
'editable' => config('pterodactyl.files.editable'), 'editable' => config('pterodactyl.files.editable'),
'current_directory' => $directory, 'current_directory' => $requestDirectory,
]); ]);
} }
} }

View file

@ -1,5 +1,17 @@
<template> <template>
<div> <div>
<div class="filemanager-breadcrumbs">
/<span class="px-1">home</span><!--
-->/<router-link :to="{ name: 'server-files' }" class="px-1">container</router-link><!--
--><span v-for="crumb in breadcrumbs" class="inline-block">
<span v-if="crumb.path">
/<router-link :to="{ name: 'server-files', params: { path: crumb.path } }" class="px-1">{{crumb.directoryName}}</router-link>
</span>
<span v-else>
/<span class="px-1 font-semibold">{{crumb.directoryName}}</span>
</span>
</span>
</div>
<div v-if="loading"> <div v-if="loading">
<div class="spinner spinner-xl blue"></div> <div class="spinner spinner-xl blue"></div>
</div> </div>
@ -14,28 +26,40 @@
<div class="flex-1 text-right">Modified</div> <div class="flex-1 text-right">Modified</div>
<div class="flex-none w-1/6">Actions</div> <div class="flex-none w-1/6">Actions</div>
</div> </div>
<div class="row clickable" v-for="directory in directories" v-on:click="currentDirectory = directory.name"> <div v-if="!directories.length && !files.length">
<div class="flex-none icon"><folder-icon/></div> <p class="text-grey text-sm text-center p-6 pb-4">This directory is empty.</p>
<div class="flex-1">{{directory.name}}</div>
<div class="flex-1 text-right text-grey-dark"></div>
<div class="flex-1 text-right text-grey-dark">{{formatDate(directory.modified)}}</div>
<div class="flex-none w-1/6"></div>
</div> </div>
<div class="row" v-for="file in files" :class="{ clickable: canEdit(file) }"> <div v-else>
<div class="flex-none icon"> <router-link class="row clickable"
<file-text-icon v-if="!file.symlink"/> v-for="directory in directories"
<link2-icon v-else/> :to="{ name: 'server-files', params: { path: getClickablePath(directory.name).replace(/^\//, '') }}"
:key="directory.name + directory.modified"
>
<div class="flex-none icon">
<folder-icon/>
</div>
<div class="flex-1">{{directory.name}}</div>
<div class="flex-1 text-right text-grey-dark"></div>
<div class="flex-1 text-right text-grey-dark">{{formatDate(directory.modified)}}</div>
<div class="flex-none w-1/6"></div>
</router-link>
<div class="row" v-for="file in files" :class="{ clickable: canEdit(file) }">
<div class="flex-none icon">
<file-text-icon v-if="!file.symlink"/>
<link2-icon v-else/>
</div>
<div class="flex-1">{{file.name}}</div>
<div class="flex-1 text-right text-grey-dark">{{readableSize(file.size)}}</div>
<div class="flex-1 text-right text-grey-dark">{{formatDate(file.modified)}}</div>
<div class="flex-none w-1/6"></div>
</div> </div>
<div class="flex-1">{{file.name}}</div>
<div class="flex-1 text-right text-grey-dark">{{readableSize(file.size)}}</div>
<div class="flex-1 text-right text-grey-dark">{{formatDate(file.modified)}}</div>
<div class="flex-none w-1/6"></div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import _ from 'lodash';
import filter from 'lodash/filter'; import filter from 'lodash/filter';
import isObject from 'lodash/isObject'; import isObject from 'lodash/isObject';
import format from 'date-fns/format'; import format from 'date-fns/format';
@ -44,14 +68,43 @@
export default { export default {
name: 'file-manager-page', name: 'file-manager-page',
components: { FileTextIcon, FolderIcon, Link2Icon }, components: {FileTextIcon, FolderIcon, Link2Icon},
computed: { computed: {
...mapState('server', ['server', 'credentials']), ...mapState('server', ['server', 'credentials']),
...mapState('socket', ['connected']), ...mapState('socket', ['connected']),
/**
* Configure the breadcrumbs that display on the filemanager based on the directory that the
* user is currently in.
*/
breadcrumbs: function () {
const directories = this.currentDirectory.replace(/^\/|\/$/, '').split('/');
if (directories.length < 1 || !directories[0]) {
return [];
}
return _.map(directories, function (value, key) {
if (key === directories.length - 1) {
return {directoryName: value};
}
return {
directoryName: value,
path: directories.slice(0, key + 1).join('/'),
};
});
}
}, },
watch: { watch: {
/**
* When the route changes reload the directory.
*/
'$route': function (to) {
this.currentDirectory = to.params.path || '/';
},
/** /**
* Watch the current directory setting and when it changes update the file listing. * Watch the current directory setting and when it changes update the file listing.
*/ */
@ -72,7 +125,7 @@
data: function () { data: function () {
return { return {
currentDirectory: '/', currentDirectory: this.$route.params.path || '/',
loading: true, loading: true,
errorMessage: null, errorMessage: null,
@ -95,7 +148,7 @@
window.axios.get(this.route('server.files', { window.axios.get(this.route('server.files', {
server: this.$route.params.id, server: this.$route.params.id,
directory: this.currentDirectory, directory: encodeURI(this.currentDirectory.replace(/^\/|\/$/, '')),
})) }))
.then((response) => { .then((response) => {
this.files = filter(response.data.contents, function (o) { this.files = filter(response.data.contents, function (o) {
@ -110,7 +163,12 @@
this.errorMessage = null; this.errorMessage = null;
}) })
.catch(err => { .catch(err => {
console.error({ err }); console.error({err});
if (err.response.status === 404) {
this.errorMessage = 'The directory you requested could not be located on the server.';
return;
}
if (err.response.data && isObject(err.response.data.errors)) { if (err.response.data && isObject(err.response.data.errors)) {
err.response.data.errors.forEach(error => { err.response.data.errors.forEach(error => {
this.errorMessage = error.detail; this.errorMessage = error.detail;
@ -132,6 +190,15 @@
return this.editableFiles.indexOf(file.mime) >= 0; return this.editableFiles.indexOf(file.mime) >= 0;
}, },
/**
* Return a formatted directory path that is used to switch to a nested directory.
*
* @return {String}
*/
getClickablePath (directory) {
return `${this.currentDirectory.replace(/\/$/, '')}/${directory}`;
},
/** /**
* Return the human readable filesize for a given number of bytes. This * Return the human readable filesize for a given number of bytes. This
* uses 1024 as the base, so the response is denoted accordingly. * uses 1024 as the base, so the response is denoted accordingly.
@ -152,7 +219,7 @@
u++; u++;
} while (Math.abs(bytes) >= 1024 && u < units.length - 1); } while (Math.abs(bytes) >= 1024 && u < units.length - 1);
return `${bytes.toFixed(1)} ${units[u]}` return `${bytes.toFixed(1)} ${units[u]}`;
}, },
/** /**

View file

@ -41,7 +41,7 @@ const routes = [
{ path: '/server/:id', component: Server, { path: '/server/:id', component: Server,
children: [ children: [
{ name: 'server', path: '', component: ConsolePage }, { name: 'server', path: '', component: ConsolePage },
{ name: 'server-files', path: 'files', component: FileManagerPage }, { name: 'server-files', path: 'files/:path(.*)?', component: FileManagerPage },
{ name: 'server-subusers', path: 'subusers', component: ServerSubusers }, { name: 'server-subusers', path: 'subusers', component: ServerSubusers },
{ name: 'server-schedules', path: 'schedules', component: ServerSchedules }, { name: 'server-schedules', path: 'schedules', component: ServerSchedules },
{ name: 'server-databases', path: 'databases', component: ServerDatabases }, { name: 'server-databases', path: 'databases', component: ServerDatabases },

View file

@ -1,5 +1,5 @@
.filemanager { .filemanager {
& > .header { & .header {
@apply .flex .text-sm .pb-4 .font-bold .border-b .border-grey-light .mb-3; @apply .flex .text-sm .pb-4 .font-bold .border-b .border-grey-light .mb-3;
& > div { & > div {
@ -7,15 +7,15 @@
} }
} }
& > .row { & .row {
@apply .flex .text-sm .py-3 .text-sm .border .border-transparent; @apply .flex .text-sm .py-3 .text-sm .border .border-transparent .text-black;
& > div { & > div {
@apply .pr-4; @apply .pr-4;
} }
&.clickable { &.clickable {
@apply .rounded .cursor-pointer; @apply .rounded .cursor-pointer .no-underline;
&:hover { &:hover {
@apply .bg-grey-lightest .border-blue-light .text-blue-dark; @apply .bg-grey-lightest .border-blue-light .text-blue-dark;
@ -30,3 +30,15 @@
} }
} }
} }
.filemanager-breadcrumbs {
@apply .px-4 .py-3 .mb-6 .rounded .border .bg-grey-lightest .text-grey-darker;
& a {
@apply .no-underline .text-blue-light;
&:hover {
@apply .text-blue-dark;
}
}
}