More socket and console improvements for server

This commit is contained in:
Dane Everitt 2018-08-01 23:37:14 -07:00
parent f866ad5b34
commit 38d7985e66
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
7 changed files with 169 additions and 76 deletions

View file

@ -8,6 +8,7 @@
"vue": "^2.5.7", "vue": "^2.5.7",
"vue-axios": "^2.1.1", "vue-axios": "^2.1.1",
"vue-router": "^3.0.1", "vue-router": "^3.0.1",
"vue-socket.io-extended": "^3.1.0",
"vuex": "^3.0.1", "vuex": "^3.0.1",
"vuex-i18n": "^1.10.5", "vuex-i18n": "^1.10.5",
"vuex-router-sync": "^5.0.0", "vuex-router-sync": "^5.0.0",

View file

@ -67,34 +67,30 @@
import Navigation from '../core/Navigation'; import Navigation from '../core/Navigation';
import ProgressBar from './components/ProgressBar'; import ProgressBar from './components/ProgressBar';
import {mapState} from 'vuex'; import {mapState} from 'vuex';
import { mapState } from 'vuex';
import VueSocketio from 'vue-socket.io-extended';
import io from 'socket.io-client'; import io from 'socket.io-client';
import Vue from 'vue';
import PowerButtons from './components/PowerButtons';
export default { export default {
components: { components: {
ProgressBar, Navigation, TerminalIcon, FolderIcon, UsersIcon, PowerButtons, ProgressBar, Navigation,
CalendarIcon, DatabaseIcon, GlobeIcon, SettingsIcon TerminalIcon, FolderIcon, UsersIcon, CalendarIcon, DatabaseIcon, GlobeIcon, SettingsIcon
}, },
computed: { computed: {
...mapState('server', ['server', 'credentials']), ...mapState('server', ['server', 'credentials']),
...mapState('socket', ['connected', 'connectionError']),
}, },
mounted: function () { mounted: function () {
this.loadServer(); this.loadServer();
this.$on('send-command', data => {
this.socket.emit('send command', data);
});
this.$on('send-initial-log', () => {
this.socket.emit('send server log');
})
}, },
data: function () { data: function () {
return { return {
socket: null,
loadingServerData: true, loadingServerData: true,
}; };
}, },
@ -109,50 +105,17 @@
this.$store.dispatch('server/getCredentials', {server: this.$route.params.id}) this.$store.dispatch('server/getCredentials', {server: this.$route.params.id})
]) ])
.then(() => { .then(() => {
// Configure the socket.io implementation. This is a really ghetto way of handling things
// but all of these plugins assume you have some constant connection, which we don't.
const socket = io(`${this.credentials.node}/v1/ws/${this.server.uuid}`, {
query: `token=${this.credentials.key}`,
});
Vue.use(VueSocketio, socket, { store: this.$store });
this.loadingServerData = false; this.loadingServerData = false;
this.initalizeWebsocket();
}) })
.catch(console.error); .catch(console.error);
}, },
initalizeWebsocket: function () {
this.socket = io(this.credentials.node + '/v1/ws/' + this.server.uuid, {
query: 'token=' + this.credentials.key,
});
this.socket.on('error', this._socket_error);
this.socket.on('connect', this._socket_connect);
this.socket.on('status', this._socket_status);
this.socket.on('initial status', this._socket_status);
this.socket.on('server log', this._socket_serverLog);
this.socket.on('console', this._socket_consoleLine);
},
_socket_error: function (err) {
this.$emit('socket::error', {err});
},
_socket_connect: function () {
this.$emit('socket::connected');
},
_socket_status: function (data) {
this.$emit('socket::status', {data});
},
_socket_serverLog: function (data) {
data.split(/\n/g).forEach(item => {
this.$emit('console', item);
});
},
_socket_consoleLine: function (data) {
if(data.line) {
data.line.split(/\n/g).forEach(item => {
this.$emit('console', item);
});
}
},
}, },
} }
</script> </script>

View file

@ -0,0 +1,61 @@
<template>
<div>
<div v-if="connected">
<transition name="slide-fade" mode="out-in">
<button class="btn btn-green uppercase text-xs px-4 py-2"
v-if="status === statuses.STATUS_OFF"
v-on:click.prevent="sendPowerAction('start')"
>Start</button>
<div v-else>
<button class="btn btn-red uppercase text-xs px-4 py-2" v-on:click.prevent="sendPowerAction('stop')">Stop</button>
<button class="btn btn-secondary uppercase text-xs px-4 py-2" v-on:click.prevent="sendPowerAction('restart')">Restart</button>
<button class="btn btn-secondary uppercase text-xs px-4 py-2" v-on:click.prevent="sendPowerAction('kill')">Kill</button>
</div>
</transition>
</div>
<div v-else>
<div class="text-center">
<div class="spinner"></div>
<div class="pt-2 text-xs text-grey-light">Connecting to node</div>
</div>
</div>
</div>
</template>
<script>
import Status from './../../../helpers/statuses';
import { mapState } from 'vuex';
export default {
name: 'power-buttons',
computed: {
...mapState('socket', ['connected', 'status']),
},
data: function () {
return {
statuses: Status,
};
},
methods: {
sendPowerAction: function (action) {
this.$socket.emit('set status', action)
},
},
};
</script>
<style scoped>
.slide-fade-enter-active {
transition: all 250ms ease;
}
.slide-fade-leave-active {
transition: all 250ms cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to {
transform: translateX(10px);
opacity: 0;
}
</style>

View file

@ -2,7 +2,7 @@
<div> <div>
<div class="text-xs font-mono"> <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="rounded-t p-2 bg-black overflow-scroll w-full" style="min-height: 16rem;max-height:64rem;">
<div v-if="loadingConsole"> <div v-if="!connected">
<div class="spinner spinner-xl mt-24"></div> <div class="spinner spinner-xl mt-24"></div>
</div> </div>
<div class="mb-2 text-grey-light" ref="terminal"></div> <div class="mb-2 text-grey-light" ref="terminal"></div>
@ -27,36 +27,59 @@
<script> <script>
import { Terminal } from 'xterm'; import { Terminal } from 'xterm';
import * as TerminalFit from 'xterm/lib/addons/fit/fit'; import * as TerminalFit from 'xterm/lib/addons/fit/fit';
import Status from './../../../helpers/statuses'; import {mapState} from 'vuex';
Terminal.applyAddon(TerminalFit); Terminal.applyAddon(TerminalFit);
export default { export default {
name: 'console-page', name: 'console-page',
computed: {
...mapState('socket', ['connected']),
},
watch: {
/**
* Watch the connected variable and when it becomes true request the server logs.
*
* @param {Boolean} state
*/
connected: function (state) {
if (state) {
this.$socket.emit('send server log');
}
}
},
/**
* Listen for specific socket.io emits from the server.
*/
sockets: {
'server log': function (data) {
data.split(/\n/g).forEach(line => {
this.terminal.writeln(line);
});
},
'console': function (data) {
data.line.split(/\n/g).forEach(line => {
this.terminal.writeln(line);
});
}
},
/** /**
* Mount the component and setup all of the terminal actions. Also fetches the initial * Mount the component and setup all of the terminal actions. Also fetches the initial
* logs from the server to populate into the terminal. * 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 () { mounted: function () {
this.$parent.$on('socket::connected', () => {
this.terminal.open(this.$refs.terminal); this.terminal.open(this.$refs.terminal);
this.terminal.fit(); this.terminal.fit();
this.terminal.clear(); this.terminal.clear();
this.$parent.$emit('send-initial-log'); if (this.connected) {
}); this.$socket.emit('send server log');
this.$parent.$on('console', data => {
this.loadingConsole = false;
this.terminal.writeln(data);
});
this.$parent.$on('socket::status', s => {
if (s === Status.STATUS_OFF) {
this.loadingConsole = false;
} }
});
}, },
data: function () { data: function () {
@ -76,7 +99,6 @@
command: '', command: '',
commandHistory: [], commandHistory: [],
commandHistoryIndex: -1, commandHistoryIndex: -1,
loadingConsole: true,
}; };
}, },
@ -87,7 +109,7 @@
sendCommand: function () { sendCommand: function () {
this.commandHistoryIndex = -1; this.commandHistoryIndex = -1;
this.commandHistory.unshift(this.command); this.commandHistory.unshift(this.command);
this.$parent.$emit('send-command', this.command); this.$socket.emit('send command', this.command);
this.command = ''; this.command = '';
}, },

View file

@ -3,12 +3,13 @@ import Vuex from 'vuex';
import auth from './modules/auth'; import auth from './modules/auth';
import dashboard from './modules/dashboard'; import dashboard from './modules/dashboard';
import server from './modules/server'; import server from './modules/server';
import socket from './modules/socket';
Vue.use(Vuex); Vue.use(Vuex);
const store = new Vuex.Store({ const store = new Vuex.Store({
strict: process.env.NODE_ENV !== 'production', strict: process.env.NODE_ENV !== 'production',
modules: {auth, dashboard, server}, modules: {auth, dashboard, server, socket},
}); });
if (module.hot) { if (module.hot) {
@ -16,9 +17,15 @@ if (module.hot) {
const newAuthModule = require('./modules/auth').default; const newAuthModule = require('./modules/auth').default;
const newDashboardModule = require('./modules/dashboard').default; const newDashboardModule = require('./modules/dashboard').default;
const newServerModule = require('./modules/server').default; const newServerModule = require('./modules/server').default;
const newSocketModule = require('./modules/socket').default;
store.hotUpdate({ store.hotUpdate({
modules: {newAuthModule, newDashboardModule, newServerModule}, modules: {
auth: newAuthModule,
dashboard: newDashboardModule,
server: newServerModule,
socket: newSocketModule
},
}); });
}); });
} }

View file

@ -0,0 +1,29 @@
import Status from './../../helpers/statuses';
export default {
namespaced: true,
state: {
connected: false,
connectionError: null,
status: Status.STATUS_OFF,
},
actions: {
},
mutations: {
SOCKET_CONNECT: (state) => {
state.connected = true;
},
SOCKET_ERROR: (state, err) => {
state.connectionError = err;
},
'SOCKET_INITIAL STATUS': (state, data) => {
state.status = data.status;
},
SOCKET_STATUS: (state, data) => {
state.status = data.status;
}
},
};

View file

@ -1490,6 +1490,10 @@ camelcase@^4.0.0, camelcase@^4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
camelcase@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
caniuse-api@^1.5.2: caniuse-api@^1.5.2:
version "1.6.1" version "1.6.1"
resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c"
@ -6876,6 +6880,12 @@ vue-router@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9" resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9"
vue-socket.io-extended@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/vue-socket.io-extended/-/vue-socket.io-extended-3.1.0.tgz#0c1833377f902381c861c43a403a476eee542bc9"
dependencies:
camelcase "^5.0.0"
vue-style-loader@^4.0.1: vue-style-loader@^4.0.1:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.0.tgz#7588bd778e2c9f8d87bfc3c5a4a039638da7a863" resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.0.tgz#7588bd778e2c9f8d87bfc3c5a4a039638da7a863"