forked from Alex/Pterodactyl-Panel
More socket and console improvements for server
This commit is contained in:
parent
f866ad5b34
commit
38d7985e66
@ -8,6 +8,7 @@
|
||||
"vue": "^2.5.7",
|
||||
"vue-axios": "^2.1.1",
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-socket.io-extended": "^3.1.0",
|
||||
"vuex": "^3.0.1",
|
||||
"vuex-i18n": "^1.10.5",
|
||||
"vuex-router-sync": "^5.0.0",
|
||||
|
@ -67,34 +67,30 @@
|
||||
import Navigation from '../core/Navigation';
|
||||
import ProgressBar from './components/ProgressBar';
|
||||
import {mapState} from 'vuex';
|
||||
|
||||
import { mapState } from 'vuex';
|
||||
import VueSocketio from 'vue-socket.io-extended';
|
||||
import io from 'socket.io-client';
|
||||
import Vue from 'vue';
|
||||
|
||||
import PowerButtons from './components/PowerButtons';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProgressBar, Navigation, TerminalIcon, FolderIcon, UsersIcon,
|
||||
CalendarIcon, DatabaseIcon, GlobeIcon, SettingsIcon
|
||||
PowerButtons, ProgressBar, Navigation,
|
||||
TerminalIcon, FolderIcon, UsersIcon, CalendarIcon, DatabaseIcon, GlobeIcon, SettingsIcon
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState('server', ['server', 'credentials']),
|
||||
...mapState('socket', ['connected', 'connectionError']),
|
||||
},
|
||||
|
||||
mounted: function () {
|
||||
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 () {
|
||||
return {
|
||||
socket: null,
|
||||
loadingServerData: true,
|
||||
};
|
||||
},
|
||||
@ -109,50 +105,17 @@
|
||||
this.$store.dispatch('server/getCredentials', {server: this.$route.params.id})
|
||||
])
|
||||
.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.initalizeWebsocket();
|
||||
})
|
||||
.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>
|
||||
|
@ -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>
|
@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<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 v-if="loadingConsole">
|
||||
<div v-if="!connected">
|
||||
<div class="spinner spinner-xl mt-24"></div>
|
||||
</div>
|
||||
<div class="mb-2 text-grey-light" ref="terminal"></div>
|
||||
@ -27,36 +27,59 @@
|
||||
<script>
|
||||
import { Terminal } from 'xterm';
|
||||
import * as TerminalFit from 'xterm/lib/addons/fit/fit';
|
||||
import Status from './../../../helpers/statuses';
|
||||
import {mapState} from 'vuex';
|
||||
|
||||
Terminal.applyAddon(TerminalFit);
|
||||
|
||||
export default {
|
||||
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
|
||||
* 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 () {
|
||||
this.$parent.$on('socket::connected', () => {
|
||||
this.terminal.open(this.$refs.terminal);
|
||||
this.terminal.fit();
|
||||
this.terminal.clear();
|
||||
|
||||
this.$parent.$emit('send-initial-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;
|
||||
if (this.connected) {
|
||||
this.$socket.emit('send server log');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
data: function () {
|
||||
@ -76,7 +99,6 @@
|
||||
command: '',
|
||||
commandHistory: [],
|
||||
commandHistoryIndex: -1,
|
||||
loadingConsole: true,
|
||||
};
|
||||
},
|
||||
|
||||
@ -87,7 +109,7 @@
|
||||
sendCommand: function () {
|
||||
this.commandHistoryIndex = -1;
|
||||
this.commandHistory.unshift(this.command);
|
||||
this.$parent.$emit('send-command', this.command);
|
||||
this.$socket.emit('send command', this.command);
|
||||
this.command = '';
|
||||
},
|
||||
|
||||
|
@ -3,12 +3,13 @@ import Vuex from 'vuex';
|
||||
import auth from './modules/auth';
|
||||
import dashboard from './modules/dashboard';
|
||||
import server from './modules/server';
|
||||
import socket from './modules/socket';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const store = new Vuex.Store({
|
||||
strict: process.env.NODE_ENV !== 'production',
|
||||
modules: {auth, dashboard, server},
|
||||
modules: {auth, dashboard, server, socket},
|
||||
});
|
||||
|
||||
if (module.hot) {
|
||||
@ -16,9 +17,15 @@ if (module.hot) {
|
||||
const newAuthModule = require('./modules/auth').default;
|
||||
const newDashboardModule = require('./modules/dashboard').default;
|
||||
const newServerModule = require('./modules/server').default;
|
||||
const newSocketModule = require('./modules/socket').default;
|
||||
|
||||
store.hotUpdate({
|
||||
modules: {newAuthModule, newDashboardModule, newServerModule},
|
||||
modules: {
|
||||
auth: newAuthModule,
|
||||
dashboard: newDashboardModule,
|
||||
server: newServerModule,
|
||||
socket: newSocketModule
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
29
resources/assets/scripts/store/modules/socket.js
Normal file
29
resources/assets/scripts/store/modules/socket.js
Normal 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;
|
||||
}
|
||||
},
|
||||
};
|
10
yarn.lock
10
yarn.lock
@ -1490,6 +1490,10 @@ camelcase@^4.0.0, camelcase@^4.1.0:
|
||||
version "4.1.0"
|
||||
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:
|
||||
version "1.6.1"
|
||||
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"
|
||||
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:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.0.tgz#7588bd778e2c9f8d87bfc3c5a4a039638da7a863"
|
||||
|
Loading…
Reference in New Issue
Block a user