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);
$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 {
$contents = $this->fileRepository->setServer($server)->setToken(
@ -63,7 +51,7 @@ class FileController extends Controller
return JsonResponse::create([
'contents' => $contents,
'editable' => config('pterodactyl.files.editable'),
'current_directory' => $directory,
'current_directory' => $requestDirectory,
]);
}
}

View file

@ -1,5 +1,17 @@
<template>
<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 class="spinner spinner-xl blue"></div>
</div>
@ -14,28 +26,40 @@
<div class="flex-1 text-right">Modified</div>
<div class="flex-none w-1/6">Actions</div>
</div>
<div class="row clickable" v-for="directory in directories" v-on:click="currentDirectory = directory.name">
<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>
<div v-if="!directories.length && !files.length">
<p class="text-grey text-sm text-center p-6 pb-4">This directory is empty.</p>
</div>
<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 v-else>
<router-link class="row clickable"
v-for="directory in directories"
: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 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>
</template>
<script>
import _ from 'lodash';
import filter from 'lodash/filter';
import isObject from 'lodash/isObject';
import format from 'date-fns/format';
@ -44,14 +68,43 @@
export default {
name: 'file-manager-page',
components: { FileTextIcon, FolderIcon, Link2Icon },
components: {FileTextIcon, FolderIcon, Link2Icon},
computed: {
...mapState('server', ['server', 'credentials']),
...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: {
/**
* 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.
*/
@ -72,7 +125,7 @@
data: function () {
return {
currentDirectory: '/',
currentDirectory: this.$route.params.path || '/',
loading: true,
errorMessage: null,
@ -95,7 +148,7 @@
window.axios.get(this.route('server.files', {
server: this.$route.params.id,
directory: this.currentDirectory,
directory: encodeURI(this.currentDirectory.replace(/^\/|\/$/, '')),
}))
.then((response) => {
this.files = filter(response.data.contents, function (o) {
@ -110,7 +163,12 @@
this.errorMessage = null;
})
.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)) {
err.response.data.errors.forEach(error => {
this.errorMessage = error.detail;
@ -132,6 +190,15 @@
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
* uses 1024 as the base, so the response is denoted accordingly.
@ -152,7 +219,7 @@
u++;
} 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,
children: [
{ 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-schedules', path: 'schedules', component: ServerSchedules },
{ name: 'server-databases', path: 'databases', component: ServerDatabases },

View file

@ -1,5 +1,5 @@
.filemanager {
& > .header {
& .header {
@apply .flex .text-sm .pb-4 .font-bold .border-b .border-grey-light .mb-3;
& > div {
@ -7,15 +7,15 @@
}
}
& > .row {
@apply .flex .text-sm .py-3 .text-sm .border .border-transparent;
& .row {
@apply .flex .text-sm .py-3 .text-sm .border .border-transparent .text-black;
& > div {
@apply .pr-4;
}
&.clickable {
@apply .rounded .cursor-pointer;
@apply .rounded .cursor-pointer .no-underline;
&:hover {
@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;
}
}
}