More filemanager work, directory browsing working
This commit is contained in:
parent
ceef2edf2e
commit
e9f8751c4c
4 changed files with 104 additions and 37 deletions
|
@ -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,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,13 +26,23 @@
|
||||||
<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>
|
||||||
|
<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">{{directory.name}}</div>
|
||||||
<div class="flex-1 text-right text-grey-dark"></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-1 text-right text-grey-dark">{{formatDate(directory.modified)}}</div>
|
||||||
<div class="flex-none w-1/6"></div>
|
<div class="flex-none w-1/6"></div>
|
||||||
</div>
|
</router-link>
|
||||||
<div class="row" v-for="file in files" :class="{ clickable: canEdit(file) }">
|
<div class="row" v-for="file in files" :class="{ clickable: canEdit(file) }">
|
||||||
<div class="flex-none icon">
|
<div class="flex-none icon">
|
||||||
<file-text-icon v-if="!file.symlink"/>
|
<file-text-icon v-if="!file.symlink"/>
|
||||||
|
@ -33,9 +55,11 @@
|
||||||
</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';
|
||||||
|
@ -49,9 +73,38 @@
|
||||||
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) {
|
||||||
|
@ -111,6 +164,11 @@
|
||||||
})
|
})
|
||||||
.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]}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 },
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue