Move everything back to vue SFCs

This commit is contained in:
Dane Everitt 2019-02-09 21:14:58 -08:00
parent 761704408e
commit 5bff8d99cc
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
58 changed files with 2558 additions and 2518 deletions

View file

@ -1,186 +0,0 @@
import Vue from 'vue';
import {mapState} from "vuex";
import {Terminal} from 'xterm';
import * as TerminalFit from 'xterm/lib/addons/fit/fit';
import {Socketio} from "@/mixins/socketio";
type DataStructure = {
terminal: Terminal | null,
command: string,
commandHistory: Array<string>,
commandHistoryIndex: number,
}
export default Vue.component('server-console', {
mixins: [Socketio],
computed: {
...mapState('socket', ['connected']),
},
watch: {
/**
* Watch the connected variable and when it becomes true request the server logs.
*/
connected: function (state: boolean) {
if (state) {
this.$nextTick(() => {
this.mountTerminal();
});
} else {
this.terminal && this.terminal.clear();
}
},
},
/**
* Listen for specific socket.io emits from the server.
*/
sockets: {
'server log': function (data: string) {
data.split(/\n/g).forEach((line: string): void => {
if (this.terminal) {
this.terminal.writeln(line + '\u001b[0m');
}
});
},
'console': function (data: { line: string }) {
data.line.split(/\n/g).forEach((line: string): void => {
if (this.terminal) {
this.terminal.writeln(line + '\u001b[0m');
}
});
},
},
/**
* Mount the component and setup all of the terminal actions. Also fetches the initial
* logs from the server to populate into the terminal if the socket is connected. If the
* socket is not connected this will occur automatically when it connects.
*/
mounted: function () {
if (this.connected) {
this.mountTerminal();
}
},
data: function (): DataStructure {
return {
terminal: null,
command: '',
commandHistory: [],
commandHistoryIndex: -1,
};
},
methods: {
/**
* Mount the terminal and grab the most recent server logs.
*/
mountTerminal: function () {
// Get a new instance of the terminal setup.
this.terminal = this._terminalInstance();
this.terminal.open((this.$refs.terminal as HTMLElement));
// @ts-ignore
this.terminal.fit();
this.terminal.clear();
this.$socket().instance().emit('send server log');
},
/**
* Send a command to the server using the configured websocket.
*/
sendCommand: function () {
this.commandHistoryIndex = -1;
this.commandHistory.unshift(this.command);
this.$socket().instance().emit('send command', this.command);
this.command = '';
},
/**
* Handle a user pressing up/down arrows when in the command field to scroll through thier
* command history for this server.
*/
handleArrowKey: function (e: KeyboardEvent) {
if (['ArrowUp', 'ArrowDown'].indexOf(e.key) < 0 || e.key === 'ArrowDown' && this.commandHistoryIndex < 0) {
return;
}
e.preventDefault();
e.stopPropagation();
if (e.key === 'ArrowUp' && (this.commandHistoryIndex + 1 > (this.commandHistory.length - 1))) {
return;
}
this.commandHistoryIndex += (e.key === 'ArrowUp') ? 1 : -1;
this.command = this.commandHistoryIndex < 0 ? '' : this.commandHistory[this.commandHistoryIndex];
},
/**
* Returns a new instance of the terminal to be used.
*
* @private
*/
_terminalInstance() {
Terminal.applyAddon(TerminalFit);
return new Terminal({
disableStdin: true,
cursorStyle: 'underline',
allowTransparency: true,
fontSize: 12,
fontFamily: 'Menlo, Monaco, Consolas, monospace',
rows: 30,
theme: {
background: 'transparent',
cursor: 'transparent',
black: '#000000',
red: '#E54B4B',
green: '#9ECE58',
yellow: '#FAED70',
blue: '#396FE2',
magenta: '#BB80B3',
cyan: '#2DDAFD',
white: '#d0d0d0',
brightBlack: 'rgba(255, 255, 255, 0.2)',
brightRed: '#FF5370',
brightGreen: '#C3E88D',
brightYellow: '#FFCB6B',
brightBlue: '#82AAFF',
brightMagenta: '#C792EA',
brightCyan: '#89DDFF',
brightWhite: '#ffffff',
},
});
}
},
template: `
<div class="animate fadein shadow-md">
<div class="text-xs font-mono">
<div class="rounded-t p-2 bg-black overflow-scroll w-full" style="min-height: 16rem;max-height:64rem;">
<div class="mb-2 text-neutral-400" ref="terminal" v-if="connected"></div>
<div v-else>
<div class="spinner spinner-xl mt-24"></div>
</div>
</div>
<div class="rounded-b bg-neutral-900 text-white flex">
<div class="flex-no-shrink p-2">
<span class="font-bold">$</span>
</div>
<div class="w-full">
<input type="text" aria-label="Send console command" class="bg-transparent text-white p-2 pl-0 w-full" placeholder="enter command and press enter to send"
ref="command"
v-model="command"
v-on:keyup.enter="sendCommand"
v-on:keydown="handleArrowKey"
>
</div>
</div>
</div>
</div>
`,
});

View file

@ -0,0 +1,189 @@
<template>
<div class="animate fadein shadow-md">
<div class="text-xs font-mono">
<div class="rounded-t p-2 bg-black overflow-scroll w-full" style="min-height: 16rem;max-height:64rem;">
<div class="mb-2 text-neutral-400" ref="terminal" v-if="connected"></div>
<div v-else>
<div class="spinner spinner-xl mt-24"></div>
</div>
</div>
<div class="rounded-b bg-neutral-900 text-white flex">
<div class="flex-no-shrink p-2">
<span class="font-bold">$</span>
</div>
<div class="w-full">
<input type="text" aria-label="Send console command" class="bg-transparent text-white p-2 pl-0 w-full" placeholder="enter command and press enter to send"
ref="command"
v-model="command"
v-on:keyup.enter="sendCommand"
v-on:keydown="handleArrowKey"
>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import {mapState} from "vuex";
import {Terminal} from 'xterm';
import * as TerminalFit from 'xterm/lib/addons/fit/fit';
import {Socketio} from "@/mixins/socketio";
type DataStructure = {
terminal: Terminal | null,
command: string,
commandHistory: Array<string>,
commandHistoryIndex: number,
}
export default Vue.extend({
name: 'ServerConsole',
mixins: [Socketio],
computed: {
...mapState('socket', ['connected']),
},
watch: {
/**
* Watch the connected variable and when it becomes true request the server logs.
*/
connected: function (state: boolean) {
if (state) {
this.$nextTick(() => {
this.mountTerminal();
});
} else {
this.terminal && this.terminal.clear();
}
},
},
/**
* Listen for specific socket.io emits from the server.
*/
sockets: {
'server log': function (data: string) {
data.split(/\n/g).forEach((line: string): void => {
if (this.terminal) {
this.terminal.writeln(line + '\u001b[0m');
}
});
},
'console': function (data: { line: string }) {
data.line.split(/\n/g).forEach((line: string): void => {
if (this.terminal) {
this.terminal.writeln(line + '\u001b[0m');
}
});
},
},
/**
* Mount the component and setup all of the terminal actions. Also fetches the initial
* logs from the server to populate into the terminal if the socket is connected. If the
* socket is not connected this will occur automatically when it connects.
*/
mounted: function () {
if (this.connected) {
this.mountTerminal();
}
},
data: function (): DataStructure {
return {
terminal: null,
command: '',
commandHistory: [],
commandHistoryIndex: -1,
};
},
methods: {
/**
* Mount the terminal and grab the most recent server logs.
*/
mountTerminal: function () {
// Get a new instance of the terminal setup.
this.terminal = this._terminalInstance();
this.terminal.open((this.$refs.terminal as HTMLElement));
// @ts-ignore
this.terminal.fit();
this.terminal.clear();
this.$socket().instance().emit('send server log');
},
/**
* Send a command to the server using the configured websocket.
*/
sendCommand: function () {
this.commandHistoryIndex = -1;
this.commandHistory.unshift(this.command);
this.$socket().instance().emit('send command', this.command);
this.command = '';
},
/**
* Handle a user pressing up/down arrows when in the command field to scroll through thier
* command history for this server.
*/
handleArrowKey: function (e: KeyboardEvent) {
if (['ArrowUp', 'ArrowDown'].indexOf(e.key) < 0 || e.key === 'ArrowDown' && this.commandHistoryIndex < 0) {
return;
}
e.preventDefault();
e.stopPropagation();
if (e.key === 'ArrowUp' && (this.commandHistoryIndex + 1 > (this.commandHistory.length - 1))) {
return;
}
this.commandHistoryIndex += (e.key === 'ArrowUp') ? 1 : -1;
this.command = this.commandHistoryIndex < 0 ? '' : this.commandHistory[this.commandHistoryIndex];
},
/**
* Returns a new instance of the terminal to be used.
*
* @private
*/
_terminalInstance() {
Terminal.applyAddon(TerminalFit);
return new Terminal({
disableStdin: true,
cursorStyle: 'underline',
allowTransparency: true,
fontSize: 12,
fontFamily: 'Menlo, Monaco, Consolas, monospace',
rows: 30,
theme: {
background: 'transparent',
cursor: 'transparent',
black: '#000000',
red: '#E54B4B',
green: '#9ECE58',
yellow: '#FAED70',
blue: '#396FE2',
magenta: '#BB80B3',
cyan: '#2DDAFD',
white: '#d0d0d0',
brightBlack: 'rgba(255, 255, 255, 0.2)',
brightRed: '#FF5370',
brightGreen: '#C3E88D',
brightYellow: '#FFCB6B',
brightBlue: '#82AAFF',
brightMagenta: '#C792EA',
brightCyan: '#89DDFF',
brightWhite: '#ffffff',
},
});
}
},
});
</script>

View file

@ -1,112 +0,0 @@
import Vue from 'vue';
import { map, filter } from 'lodash';
import Modal from '@/components/core/Modal';
import CreateDatabaseModal from './../components/database/CreateDatabaseModal';
import Icon from "@/components/core/Icon";
import {ServerDatabase} from "@/api/server/types";
import DatabaseRow from "@/components/server/components/database/DatabaseRow";
type DataStructure = {
loading: boolean,
showCreateModal: boolean,
databases: Array<ServerDatabase>,
}
export default Vue.component('server-databases', {
components: {DatabaseRow, CreateDatabaseModal, Modal, Icon },
data: function (): DataStructure {
return {
databases: [],
loading: true,
showCreateModal: false,
};
},
mounted: function () {
this.getDatabases();
window.events.$on('server:deleted-database', this.removeDatabase);
},
methods: {
/**
* Get all of the databases that exist for this server.
*/
getDatabases: function () {
this.$flash.clear();
this.loading = true;
window.axios.get(this.route('api.client.servers.databases', {
server: this.$route.params.id,
include: 'password'
}))
.then(response => {
this.databases = map(response.data.data, (object) => {
const data = object.attributes;
data.password = data.relationships.password.attributes.password;
data.showPassword = false;
delete data.relationships;
return data;
});
})
.catch(err => {
this.$flash.error('There was an error encountered while attempting to fetch databases for this server.');
console.error(err);
})
.then(() => {
this.loading = false;
});
},
/**
* Add the database to the list of existing databases automatically when the modal
* is closed with a successful callback.
*/
handleModalCallback: function (data: ServerDatabase) {
this.databases.push(data);
},
/**
* Handle event that is removing a database.
*/
removeDatabase: function (databaseId: string) {
this.databases = filter(this.databases, (database) => {
return database.id !== databaseId;
});
}
},
template: `
<div>
<div v-if="loading">
<div class="spinner spinner-xl blue"></div>
</div>
<div class="animate fadein" v-else>
<div class="content-box mb-6" v-if="!databases.length">
<div class="flex items-center">
<icon name="database" class="flex-none text-neutral-800"></icon>
<div class="flex-1 px-4 text-neutral-800">
<p>You have no databases.</p>
</div>
</div>
</div>
<div v-else>
<DatabaseRow v-for="database in databases" :database="database" :key="database.name"/>
</div>
<div>
<button class="btn btn-primary btn-lg" v-on:click="showCreateModal = true">Create new database</button>
</div>
<modal :show="showCreateModal" v-on:close="showCreateModal = false">
<CreateDatabaseModal
v-on:close="showCreateModal = false"
v-on:database="handleModalCallback"
v-if="showCreateModal"
/>
</modal>
</div>
</div>
`,
});

View file

@ -0,0 +1,115 @@
<template>
<div>
<div v-if="loading">
<div class="spinner spinner-xl blue"></div>
</div>
<div class="animate fadein" v-else>
<div class="content-box mb-6" v-if="!databases.length">
<div class="flex items-center">
<Icon name="database" class="flex-none text-neutral-800"></icon>
<div class="flex-1 px-4 text-neutral-800">
<p>You have no databases.</p>
</div>
</div>
</div>
<div v-else>
<DatabaseRow v-for="database in databases" :database="database" :key="database.name"/>
</div>
<div>
<button class="btn btn-primary btn-lg" v-on:click="showCreateModal = true">Create new database</button>
</div>
<Modal :show="showCreateModal" v-on:close="showCreateModal = false">
<CreateDatabaseModal
v-on:close="showCreateModal = false"
v-on:database="handleModalCallback"
v-if="showCreateModal"
/>
</modal>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import {filter, map} from 'lodash';
import Modal from '@/components/core/Modal.vue';
import CreateDatabaseModal from './../components/database/CreateDatabaseModal.vue';
import Icon from "@/components/core/Icon.vue";
import {ServerDatabase} from "@/api/server/types";
import DatabaseRow from "@/components/server/components/database/DatabaseRow.vue";
type DataStructure = {
loading: boolean,
showCreateModal: boolean,
databases: Array<ServerDatabase>,
}
export default Vue.extend({
name: 'ServerDatabases',
components: {DatabaseRow, CreateDatabaseModal, Modal, Icon},
data: function (): DataStructure {
return {
databases: [],
loading: true,
showCreateModal: false,
};
},
mounted: function () {
this.getDatabases();
window.events.$on('server:deleted-database', this.removeDatabase);
},
methods: {
/**
* Get all of the databases that exist for this server.
*/
getDatabases: function () {
this.$flash.clear();
this.loading = true;
window.axios.get(this.route('api.client.servers.databases', {
server: this.$route.params.id,
include: 'password'
}))
.then(response => {
this.databases = map(response.data.data, (object) => {
const data = object.attributes;
data.password = data.relationships.password.attributes.password;
data.showPassword = false;
delete data.relationships;
return data;
});
})
.catch(err => {
this.$flash.error('There was an error encountered while attempting to fetch databases for this server.');
console.error(err);
})
.then(() => {
this.loading = false;
});
},
/**
* Add the database to the list of existing databases automatically when the modal
* is closed with a successful callback.
*/
handleModalCallback: function (data: ServerDatabase) {
this.databases.push(data);
},
/**
* Handle event that is removing a database.
*/
removeDatabase: function (databaseId: string) {
this.databases = filter(this.databases, (database) => {
return database.id !== databaseId;
});
}
},
});
</script>

View file

@ -1,9 +1,62 @@
<template>
<div class="animated-fade-in">
<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 text-neutral-600 font-medium">{{crumb.directoryName}}</span>
</span>
</span>
</div>
<div class="content-box">
<div v-if="loading">
<div class="spinner spinner-xl blue"></div>
</div>
<div v-else-if="!loading && errorMessage">
<div class="alert error" v-text="errorMessage"></div>
</div>
<div v-else-if="!directories.length && !files.length">
<p class="text-neutral-500 text-sm text-center p-6 pb-4">This directory is empty.</p>
</div>
<div class="filemanager animated-fade-in" v-else>
<div class="header">
<div class="flex-none w-8"></div>
<div class="flex-1">Name</div>
<div class="flex-1 text-right">Size</div>
<div class="flex-1 text-right">Modified</div>
<div class="flex-none w-1/6">Actions</div>
</div>
<div v-for="directory in directories">
<FolderRow :directory="directory"/>
</div>
<div v-for="file in files">
<FileRow :file="file" :editable="editableFiles" />
</div>
</div>
</div>
<div class="flex mt-6" v-if="!loading && !errorMessage">
<div class="flex-1"></div>
<div class="mr-4">
<a href="#" class="block btn btn-secondary btn-sm">New Folder</a>
</div>
<div>
<a href="#" class="block btn btn-primary btn-sm">New File</a>
</div>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import {mapState} from "vuex";
import { map } from 'lodash';
import getDirectoryContents from "@/api/server/getDirectoryContents";
import FileRow from "@/components/server/components/filemanager/FileRow";
import FolderRow from "@/components/server/components/filemanager/FolderRow";
import FileRow from "@/components/server/components/filemanager/FileRow.vue";
import FolderRow from "@/components/server/components/filemanager/FolderRow.vue";
type DataStructure = {
loading: boolean,
@ -14,7 +67,8 @@ type DataStructure = {
editableFiles: Array<string>,
}
export default Vue.component('file-manager', {
export default Vue.extend({
name: 'FileManager',
components: { FileRow, FolderRow },
computed: {
...mapState('server', ['server', 'credentials']),
@ -113,56 +167,5 @@ export default Vue.component('file-manager', {
});
},
},
template: `
<div class="animated-fade-in">
<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 text-neutral-600 font-medium">{{crumb.directoryName}}</span>
</span>
</span>
</div>
<div class="content-box">
<div v-if="loading">
<div class="spinner spinner-xl blue"></div>
</div>
<div v-else-if="!loading && errorMessage">
<div class="alert error" v-text="errorMessage"></div>
</div>
<div v-else-if="!directories.length && !files.length">
<p class="text-neutral-500 text-sm text-center p-6 pb-4">This directory is empty.</p>
</div>
<div class="filemanager animated-fade-in" v-else>
<div class="header">
<div class="flex-none w-8"></div>
<div class="flex-1">Name</div>
<div class="flex-1 text-right">Size</div>
<div class="flex-1 text-right">Modified</div>
<div class="flex-none w-1/6">Actions</div>
</div>
<div v-for="directory in directories">
<folder-row :directory="directory"/>
</div>
<div v-for="file in files">
<file-row :file="file" :editable="editableFiles" />
</div>
</div>
</div>
<div class="flex mt-6" v-if="!loading && !errorMessage">
<div class="flex-1"></div>
<div class="mr-4">
<a href="#" class="block btn btn-secondary btn-sm">New Folder</a>
</div>
<div>
<a href="#" class="block btn btn-primary btn-sm">New File</a>
</div>
</div>
</div>
`,
});
</script>