Change socket implementation for servers
This commit is contained in:
parent
e0fda5865d
commit
dc52e238ac
9 changed files with 242 additions and 17 deletions
|
@ -171,7 +171,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
|
||||||
* Nest and Egg listings now show the associated ID in order to make API requests easier.
|
* Nest and Egg listings now show the associated ID in order to make API requests easier.
|
||||||
* Added star indicators to user listing in Admin CP to indicate users who are set as a root admin.
|
* Added star indicators to user listing in Admin CP to indicate users who are set as a root admin.
|
||||||
* Creating a new node will now requires a SSL connection if the Panel is configured to use SSL as well.
|
* Creating a new node will now requires a SSL connection if the Panel is configured to use SSL as well.
|
||||||
* Socketio error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure.
|
* Connector error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure.
|
||||||
* File manager now supports mass deletion option for files and folders.
|
* File manager now supports mass deletion option for files and folders.
|
||||||
* Support for CS:GO as a default service option selection.
|
* Support for CS:GO as a default service option selection.
|
||||||
* Support for GMOD as a default service option selection.
|
* Support for GMOD as a default service option selection.
|
||||||
|
@ -301,7 +301,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
|
||||||
* Changed 2FA login process to be more secure. Previously authentication checking happened on the 2FA post page, now it happens prior and is passed along to the 2FA page to avoid storing any credentials.
|
* Changed 2FA login process to be more secure. Previously authentication checking happened on the 2FA post page, now it happens prior and is passed along to the 2FA page to avoid storing any credentials.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* Socketio error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure.
|
* Connector error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure.
|
||||||
|
|
||||||
## v0.7.0-beta.1 (Derelict Dermodactylus)
|
## v0.7.0-beta.1 (Derelict Dermodactylus)
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
"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",
|
||||||
|
@ -29,6 +28,7 @@
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
"babel-plugin-transform-strict-mode": "^6.18.0",
|
"babel-plugin-transform-strict-mode": "^6.18.0",
|
||||||
"babel-register": "^6.26.0",
|
"babel-register": "^6.26.0",
|
||||||
|
"camelcase": "^5.0.0",
|
||||||
"clean-webpack-plugin": "^0.1.19",
|
"clean-webpack-plugin": "^0.1.19",
|
||||||
"css-loader": "^0.28.11",
|
"css-loader": "^0.28.11",
|
||||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||||
|
|
|
@ -70,18 +70,19 @@
|
||||||
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 VueSocketio from 'vue-socket.io-extended';
|
|
||||||
import io from 'socket.io-client';
|
import io from 'socket.io-client';
|
||||||
import Vue from 'vue';
|
import { Socketio } from './../../mixins/socketio';
|
||||||
|
|
||||||
import PowerButtons from './components/PowerButtons';
|
import PowerButtons from './components/PowerButtons';
|
||||||
import Flash from '../Flash';
|
import Flash from '../Flash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
name: 'server',
|
||||||
|
mixins: [Socketio],
|
||||||
components: {
|
components: {
|
||||||
Flash,
|
Flash,
|
||||||
PowerButtons, ProgressBar, Navigation,
|
PowerButtons, ProgressBar, Navigation,
|
||||||
TerminalIcon, FolderIcon, UsersIcon, CalendarIcon, DatabaseIcon, GlobeIcon, SettingsIcon
|
TerminalIcon, FolderIcon, UsersIcon, CalendarIcon, DatabaseIcon, GlobeIcon, SettingsIcon,
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -95,10 +96,20 @@
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sockets: {
|
||||||
|
'console': function () {
|
||||||
|
console.log('server CONSOLE');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
this.loadServer();
|
this.loadServer();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
beforeDestroy: function () {
|
||||||
|
this.removeSocket();
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
* Load the core server information needed for these pages to be functional.
|
* Load the core server information needed for these pages to be functional.
|
||||||
|
@ -115,7 +126,7 @@
|
||||||
query: `token=${this.credentials.key}`,
|
query: `token=${this.credentials.key}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
Vue.use(VueSocketio, socket, { store: this.$store });
|
this.$socket().connect(socket);
|
||||||
this.loadingServerData = false;
|
this.loadingServerData = false;
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
|
|
|
@ -24,11 +24,12 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Status from './../../../helpers/statuses';
|
import Status from './../../../helpers/statuses';
|
||||||
|
import { Socketio } from './../../../mixins/socketio';
|
||||||
import { mapState } from 'vuex';
|
import { mapState } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'power-buttons',
|
name: 'power-buttons',
|
||||||
|
mixins: [Socketio],
|
||||||
computed: {
|
computed: {
|
||||||
...mapState('socket', ['connected', 'status']),
|
...mapState('socket', ['connected', 'status']),
|
||||||
},
|
},
|
||||||
|
@ -41,7 +42,7 @@
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
sendPowerAction: function (action) {
|
sendPowerAction: function (action) {
|
||||||
this.$socket.emit('set status', action)
|
this.$socket().instance().emit('set status', action)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,10 +28,12 @@
|
||||||
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 {mapState} from 'vuex';
|
import {mapState} from 'vuex';
|
||||||
|
import {Socketio} from './../../../mixins/socketio';
|
||||||
|
|
||||||
Terminal.applyAddon(TerminalFit);
|
Terminal.applyAddon(TerminalFit);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [Socketio],
|
||||||
name: 'console-page',
|
name: 'console-page',
|
||||||
computed: {
|
computed: {
|
||||||
...mapState('socket', ['connected']),
|
...mapState('socket', ['connected']),
|
||||||
|
@ -103,7 +105,7 @@
|
||||||
this.terminal.fit();
|
this.terminal.fit();
|
||||||
this.terminal.clear();
|
this.terminal.clear();
|
||||||
|
|
||||||
this.$socket.emit('send server log');
|
this.$socket().instance().emit('send server log');
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,7 +114,7 @@
|
||||||
sendCommand: function () {
|
sendCommand: function () {
|
||||||
this.commandHistoryIndex = -1;
|
this.commandHistoryIndex = -1;
|
||||||
this.commandHistory.unshift(this.command);
|
this.commandHistory.unshift(this.command);
|
||||||
this.$socket.emit('send command', this.command);
|
this.$socket().instance().emit('send command', this.command);
|
||||||
this.command = '';
|
this.command = '';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
103
resources/assets/scripts/mixins/socketio/connector.js
Normal file
103
resources/assets/scripts/mixins/socketio/connector.js
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import io from 'socket.io-client';
|
||||||
|
import camelCase from 'camelcase';
|
||||||
|
import SocketEmitter from './emitter';
|
||||||
|
|
||||||
|
const SYSTEM_EVENTS = [
|
||||||
|
'connect',
|
||||||
|
'error',
|
||||||
|
'disconnect',
|
||||||
|
'reconnect',
|
||||||
|
'reconnect_attempt',
|
||||||
|
'reconnecting',
|
||||||
|
'reconnect_error',
|
||||||
|
'reconnect_failed',
|
||||||
|
'connect_error',
|
||||||
|
'connect_timeout',
|
||||||
|
'connecting',
|
||||||
|
'ping',
|
||||||
|
'pong',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default class SocketioConnector {
|
||||||
|
constructor (store = null) {
|
||||||
|
this.socket = null;
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a new Socket connection.
|
||||||
|
*
|
||||||
|
* @param {io} socket
|
||||||
|
*/
|
||||||
|
connect (socket) {
|
||||||
|
if (!socket instanceof io) {
|
||||||
|
throw new Error('First argument passed to connect() should be an instance of socket.io-client.');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket = socket;
|
||||||
|
this.registerEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the socket instance we are working with.
|
||||||
|
*
|
||||||
|
* @return {io|null}
|
||||||
|
*/
|
||||||
|
instance () {
|
||||||
|
return this.socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the event listeners for this socket including user-defined ones in the store as
|
||||||
|
* well as global system events from Socekt.io.
|
||||||
|
*/
|
||||||
|
registerEventListeners () {
|
||||||
|
this.socket['onevent'] = (packet) => {
|
||||||
|
const [event, ...args] = packet.data;
|
||||||
|
SocketEmitter.emit(event, ...args);
|
||||||
|
this.passToStore(event, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
SYSTEM_EVENTS.forEach((event) => {
|
||||||
|
this.socket.on(event, (payload) => {
|
||||||
|
SocketEmitter.emit(event, payload);
|
||||||
|
this.passToStore(event, payload);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass event calls off to the Vuex store if there is a corresponding function.
|
||||||
|
*
|
||||||
|
* @param {String|Number|Symbol} event
|
||||||
|
* @param {Array} payload
|
||||||
|
*/
|
||||||
|
passToStore (event, payload) {
|
||||||
|
if (!this.store) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutation = `SOCKET_${event.toUpperCase()}`;
|
||||||
|
const action = `socket_${camelCase(event)}`;
|
||||||
|
|
||||||
|
Object.keys(this.store._mutations).filter((namespaced) => {
|
||||||
|
return namespaced.split('/').pop() === mutation;
|
||||||
|
}).forEach((namespaced) => {
|
||||||
|
this.store.commit(namespaced, this.unwrap(payload));
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(this.store._actions).filter((namespaced) => {
|
||||||
|
return namespaced.split('/').pop() === action;
|
||||||
|
}).forEach((namespaced) => {
|
||||||
|
this.store.dispatch(namespaced, this.unwrap(payload));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array} args
|
||||||
|
* @return {Array<Object>|Object}
|
||||||
|
*/
|
||||||
|
unwrap (args) {
|
||||||
|
return (args && args.length <= 1) ? args[0] : args;
|
||||||
|
}
|
||||||
|
}
|
61
resources/assets/scripts/mixins/socketio/emitter.js
Normal file
61
resources/assets/scripts/mixins/socketio/emitter.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import isFunction from 'lodash/isFunction';
|
||||||
|
|
||||||
|
export default new class SocketEmitter {
|
||||||
|
constructor () {
|
||||||
|
this.listeners = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an event listener for socket events.
|
||||||
|
*
|
||||||
|
* @param {String|Number|Symbol} event
|
||||||
|
* @param {Function} callback
|
||||||
|
* @param {*} vm
|
||||||
|
*/
|
||||||
|
addListener (event, callback, vm) {
|
||||||
|
if (!isFunction(callback)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.listeners.has(event)) {
|
||||||
|
this.listeners.set(event, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listeners.get(event).push({callback, vm});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an event listener for socket events based on the context passed through.
|
||||||
|
*
|
||||||
|
* @param {String|Number|Symbol} event
|
||||||
|
* @param {Function} callback
|
||||||
|
* @param {*} vm
|
||||||
|
*/
|
||||||
|
removeListener (event, callback, vm) {
|
||||||
|
if (!isFunction(callback) || !this.listeners.has(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filtered = this.listeners.get(event).filter((listener) => {
|
||||||
|
return listener.callback !== callback || listener.vm !== vm;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filtered.length > 0) {
|
||||||
|
this.listeners.set(event, filtered);
|
||||||
|
} else {
|
||||||
|
this.listeners.delete(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a socket event.
|
||||||
|
*
|
||||||
|
* @param {String|Number|Symbol} event
|
||||||
|
* @param {Array} args
|
||||||
|
*/
|
||||||
|
emit (event, ...args) {
|
||||||
|
(this.listeners.get(event) || []).forEach((listener) => {
|
||||||
|
listener.callback.call(listener.vm, ...args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
53
resources/assets/scripts/mixins/socketio/index.js
Normal file
53
resources/assets/scripts/mixins/socketio/index.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import SocketEmitter from './emitter';
|
||||||
|
import SocketioConnector from './connector';
|
||||||
|
|
||||||
|
let connector = null;
|
||||||
|
|
||||||
|
export const Socketio = {
|
||||||
|
/**
|
||||||
|
* Setup the socket when we create the first component using the mixin. This is the Server.vue
|
||||||
|
* file, unless you mess up all of this code. Subsequent components to use this mixin will
|
||||||
|
* receive the existing connector instance, so it is very important that the top-most component
|
||||||
|
* calls the connectors destroy function when it is destroyed.
|
||||||
|
*/
|
||||||
|
created: function () {
|
||||||
|
if (!connector) {
|
||||||
|
connector = new SocketioConnector(this.$store);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sockets = this.$options.sockets || {};
|
||||||
|
Object.keys(sockets).forEach((event) => {
|
||||||
|
SocketEmitter.addListener(event, sockets[event], this);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before destroying the component we need to remove any event listeners registered for it.
|
||||||
|
*/
|
||||||
|
beforeDestroy: function () {
|
||||||
|
const sockets = this.$options.sockets || {};
|
||||||
|
Object.keys(sockets).forEach((event) => {
|
||||||
|
SocketEmitter.removeListener(event, sockets[event], this);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* @return {SocketioConnector}
|
||||||
|
*/
|
||||||
|
'$socket': function () {
|
||||||
|
return connector;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects from the active socket and sets the connector to null.
|
||||||
|
*/
|
||||||
|
removeSocket: function () {
|
||||||
|
if (connector !== null && connector.instance() !== null) {
|
||||||
|
connector.instance().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
connector = null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -6880,12 +6880,6 @@ 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"
|
||||||
|
|
Loading…
Reference in a new issue