Implement some flow and cleanup API call for file manager
This commit is contained in:
parent
c3ef290145
commit
aee42df3ad
9 changed files with 186 additions and 118 deletions
|
@ -6,6 +6,7 @@
|
||||||
"flowtype-errors/show-errors": 2,
|
"flowtype-errors/show-errors": 2,
|
||||||
"semi": "off",
|
"semi": "off",
|
||||||
"indent": ["error", 4],
|
"indent": ["error", 4],
|
||||||
"comma-dangle": ["error", "always-multiline"]
|
"comma-dangle": ["error", "always-multiline"],
|
||||||
|
"no-unused-vars": "warn"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[ignore]
|
[ignore]
|
||||||
<PROJECT_ROOT>/vendor/.*
|
<PROJECT_ROOT>/vendor/symfony/.*
|
||||||
<PROJECT_ROOT>/tests/.*
|
<PROJECT_ROOT>/tests/.*
|
||||||
<PROJECT_ROOT>/storage/.*
|
<PROJECT_ROOT>/storage/.*
|
||||||
<PROJECT_ROOT>/app/.*
|
<PROJECT_ROOT>/app/.*
|
||||||
|
|
25
resources/assets/scripts/api/http.js
Normal file
25
resources/assets/scripts/api/http.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// @flow
|
||||||
|
import axios from 'axios';
|
||||||
|
import type { AxiosInstance } from 'axios';
|
||||||
|
|
||||||
|
// This token is set in the bootstrap.js file at the beginning of the request
|
||||||
|
// and is carried through from there.
|
||||||
|
// const token: string = '';
|
||||||
|
|
||||||
|
const http: AxiosInstance = axios.create({
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
});
|
||||||
|
|
||||||
|
// If we have a phpdebugbar instance registered at this point in time go
|
||||||
|
// ahead and route the response data through to it so things show up.
|
||||||
|
if (typeof window.phpdebugbar !== 'undefined') {
|
||||||
|
http.interceptors.response.use(response => {
|
||||||
|
window.phpdebugbar.ajaxHandler.handle(response.request);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default http;
|
50
resources/assets/scripts/api/server/getDirectoryContents.js
Normal file
50
resources/assets/scripts/api/server/getDirectoryContents.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// @flow
|
||||||
|
import http from './../http';
|
||||||
|
import filter from 'lodash/filter';
|
||||||
|
import isObject from 'lodash/isObject';
|
||||||
|
import route from '../../../../../vendor/tightenco/ziggy/src/js/route';
|
||||||
|
|
||||||
|
export interface DirectoryContentsResponse {
|
||||||
|
files: Object,
|
||||||
|
directories: Object,
|
||||||
|
editable: Array<string>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the contents of a specific directory for a given server.
|
||||||
|
*
|
||||||
|
* @param {String} server
|
||||||
|
* @param {String} directory
|
||||||
|
* @return {Promise<DirectoryContentsResponse>}
|
||||||
|
*/
|
||||||
|
export function getDirectoryContents(server: string, directory: string): Promise<DirectoryContentsResponse> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
http.get(route('server.files', { server, directory }))
|
||||||
|
.then((response) => {
|
||||||
|
return resolve({
|
||||||
|
files: filter(response.data.contents, function (o) {
|
||||||
|
return o.file;
|
||||||
|
}),
|
||||||
|
directories: filter(response.data.contents, function (o) {
|
||||||
|
return o.directory;
|
||||||
|
}),
|
||||||
|
editable: response.data.editable,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (err.response && err.response.status === 404) {
|
||||||
|
return reject('The directory you requested could not be located on the server');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.response.data && isObject(err.response.data.errors)) {
|
||||||
|
err.response.data.errors.forEach(error => {
|
||||||
|
return reject(error.detail);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getDirectoryContents;
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
import vuexI18n from 'vuex-i18n';
|
import vuexI18n from 'vuex-i18n';
|
||||||
|
@ -14,7 +15,7 @@ import { flash } from './mixins/flash';
|
||||||
import store from './store/index.js';
|
import store from './store/index.js';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
|
|
||||||
window.events = new Vue;
|
window.events = new Vue();
|
||||||
window.Ziggy = Ziggy;
|
window.Ziggy = Ziggy;
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
@ -22,16 +23,18 @@ Vue.use(VueRouter);
|
||||||
Vue.use(vuexI18n.plugin, store);
|
Vue.use(vuexI18n.plugin, store);
|
||||||
Vue.use(VeeValidate);
|
Vue.use(VeeValidate);
|
||||||
|
|
||||||
|
// $FlowFixMe: this is always going to be unhappy because we ignore the vendor dir.
|
||||||
const route = require('./../../../vendor/tightenco/ziggy/src/js/route').default;
|
const route = require('./../../../vendor/tightenco/ziggy/src/js/route').default;
|
||||||
|
|
||||||
Vue.mixin({ methods: { route } });
|
Vue.mixin({ methods: { route }});
|
||||||
Vue.mixin(flash);
|
Vue.mixin(flash);
|
||||||
|
|
||||||
Vue.i18n.add('en', Locales.en);
|
Vue.i18n.add('en', Locales.en);
|
||||||
Vue.i18n.set('en');
|
Vue.i18n.set('en');
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
module.hot.accept();
|
module.hot.accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = new Vue({ store, router }).$mount('#pterodactyl');
|
new Vue({ store, router }).$mount('#pterodactyl');
|
||||||
|
|
|
@ -42,126 +42,114 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import map from 'lodash/map';
|
// @flow
|
||||||
import filter from 'lodash/filter';
|
import map from 'lodash/map';
|
||||||
import isObject from 'lodash/isObject';
|
import { mapState } from 'vuex';
|
||||||
import { mapState } from 'vuex';
|
import type { Route } from 'vue-router';
|
||||||
import FileManagerFileRow from '../components/filemanager/FileManagerFileRow';
|
import FileManagerFileRow from '../components/filemanager/FileManagerFileRow';
|
||||||
import FileManagerFolderRow from '../components/filemanager/FileManagerFolderRow';
|
import FileManagerFolderRow from '../components/filemanager/FileManagerFolderRow';
|
||||||
|
import { getDirectoryContents, DirectoryContentsResponse } from '../../../api/server/getDirectoryContents';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'file-manager-page',
|
name: 'file-manager-page',
|
||||||
components: {FileManagerFolderRow, FileManagerFileRow},
|
components: { FileManagerFolderRow, FileManagerFileRow },
|
||||||
|
|
||||||
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
|
* Configure the breadcrumbs that display on the filemanager based on the directory that the
|
||||||
* user is currently in.
|
* user is currently in.
|
||||||
*/
|
*/
|
||||||
breadcrumbs: function () {
|
breadcrumbs: function (): Array<Object> {
|
||||||
const directories = this.currentDirectory.replace(/^\/|\/$/, '').split('/');
|
const directories: Array<string> = this.currentDirectory.replace(/^\/|\/$/, '').split('/');
|
||||||
if (directories.length < 1 || !directories[0]) {
|
if (directories.length < 1 || !directories[0]) {
|
||||||
return [];
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return map(directories, function (value: string, key: number) {
|
||||||
|
if (key === directories.length - 1) {
|
||||||
|
return { directoryName: value };
|
||||||
}
|
}
|
||||||
|
|
||||||
return map(directories, function (value, key) {
|
return {
|
||||||
if (key === directories.length - 1) {
|
directoryName: value,
|
||||||
return {directoryName: value};
|
path: directories.slice(0, key + 1).join('/'),
|
||||||
}
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
return {
|
watch: {
|
||||||
directoryName: value,
|
/**
|
||||||
path: directories.slice(0, key + 1).join('/'),
|
* When the route changes reload the directory.
|
||||||
};
|
*/
|
||||||
});
|
'$route': function (to: Route) {
|
||||||
}
|
this.currentDirectory = to.params.path || '/';
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
/**
|
||||||
/**
|
* Watch the current directory setting and when it changes update the file listing.
|
||||||
* When the route changes reload the directory.
|
*/
|
||||||
*/
|
currentDirectory: function (): void {
|
||||||
'$route': function (to) {
|
|
||||||
this.currentDirectory = to.params.path || '/';
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watch the current directory setting and when it changes update the file listing.
|
|
||||||
*/
|
|
||||||
currentDirectory: function () {
|
|
||||||
this.listDirectory();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When we reconnect to the Daemon make sure we grab a listing of all of the files
|
|
||||||
* so that the error message disappears and we then load in a fresh listing.
|
|
||||||
*/
|
|
||||||
connected: function () {
|
|
||||||
if (this.connected) {
|
|
||||||
this.listDirectory();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
currentDirectory: this.$route.params.path || '/',
|
|
||||||
loading: true,
|
|
||||||
errorMessage: null,
|
|
||||||
|
|
||||||
directories: [],
|
|
||||||
editableFiles: [],
|
|
||||||
files: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted: function () {
|
|
||||||
this.listDirectory();
|
this.listDirectory();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
/**
|
||||||
/**
|
* When we reconnect to the Daemon make sure we grab a listing of all of the files
|
||||||
* List the contents of a directory.
|
* so that the error message disappears and we then load in a fresh listing.
|
||||||
*/
|
*/
|
||||||
listDirectory: function () {
|
connected: function (): void {
|
||||||
this.loading = true;
|
if (this.connected) {
|
||||||
|
this.listDirectory();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
window.axios.get(this.route('server.files', {
|
data: function () {
|
||||||
server: this.$route.params.id,
|
return {
|
||||||
directory: encodeURI(this.currentDirectory.replace(/^\/|\/$/, '')),
|
currentDirectory: this.$route.params.path || '/',
|
||||||
}))
|
loading: true,
|
||||||
.then((response) => {
|
errorMessage: null,
|
||||||
this.files = filter(response.data.contents, function (o) {
|
|
||||||
return o.file;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.directories = filter(response.data.contents, function (o) {
|
directories: [],
|
||||||
return o.directory;
|
editableFiles: [],
|
||||||
});
|
files: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
this.editableFiles = response.data.editable;
|
mounted: function () {
|
||||||
this.errorMessage = null;
|
this.listDirectory();
|
||||||
})
|
},
|
||||||
.catch(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)) {
|
methods: {
|
||||||
err.response.data.errors.forEach(error => {
|
/**
|
||||||
this.errorMessage = error.detail;
|
* List the contents of a directory.
|
||||||
});
|
*/
|
||||||
}
|
listDirectory: function (): void {
|
||||||
})
|
this.loading = true;
|
||||||
.finally(() => {
|
|
||||||
this.loading = false;
|
const directory: string = encodeURI(this.currentDirectory.replace(/^\/|\/$/, ''));
|
||||||
});
|
getDirectoryContents(this.$route.params.id, directory)
|
||||||
},
|
.then((response: DirectoryContentsResponse) => {
|
||||||
}
|
this.files = response.files;
|
||||||
};
|
this.directories = response.directories;
|
||||||
|
this.editableFiles = response.editable;
|
||||||
|
this.errorMessage = null;
|
||||||
|
})
|
||||||
|
.catch((err: string|Object) => {
|
||||||
|
if (err instanceof String) {
|
||||||
|
this.errorMessage = err;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('An error was encountered while processing this request.', { err });
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import format from 'date-fns/format';
|
import format from 'date-fns/format';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,13 +8,13 @@ import format from 'date-fns/format';
|
||||||
* @param {Number} bytes
|
* @param {Number} bytes
|
||||||
* @return {String}
|
* @return {String}
|
||||||
*/
|
*/
|
||||||
export function readableSize (bytes) {
|
export function readableSize (bytes: number): string {
|
||||||
if (Math.abs(bytes) < 1024) {
|
if (Math.abs(bytes) < 1024) {
|
||||||
return `${bytes} Bytes`;
|
return `${bytes} Bytes`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let u = -1;
|
let u: number = -1;
|
||||||
const units = ['KiB', 'MiB', 'GiB', 'TiB'];
|
const units: Array<string> = ['KiB', 'MiB', 'GiB', 'TiB'];
|
||||||
|
|
||||||
do {
|
do {
|
||||||
bytes /= 1024;
|
bytes /= 1024;
|
||||||
|
@ -29,6 +30,6 @@ export function readableSize (bytes) {
|
||||||
* @param {String} date
|
* @param {String} date
|
||||||
* @return {String}
|
* @return {String}
|
||||||
*/
|
*/
|
||||||
export function formatDate (date) {
|
export function formatDate (date: string): string {
|
||||||
return format(date, 'MMM D, YYYY [at] HH:MM');
|
return format(date, 'MMM D, YYYY [at] HH:MM');
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -15,7 +15,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.clickable {
|
&.clickable {
|
||||||
@apply .cursor-pointer .no-underline;
|
@apply .no-underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active-selection, &.clickable:hover {
|
&.active-selection, &.clickable:hover {
|
||||||
|
|
Loading…
Reference in a new issue