1
1
mirror of https://github.com/pterodactyl/panel.git synced 2024-11-22 17:12:30 +01:00

Change socket implementation for servers

This commit is contained in:
Dane Everitt 2018-08-18 20:13:40 -07:00
parent e0fda5865d
commit dc52e238ac
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
9 changed files with 242 additions and 17 deletions

View File

@ -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.
* 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.
* 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.
* Support for CS:GO 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.
### 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)
### Added

View File

@ -8,7 +8,6 @@
"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",
@ -29,6 +28,7 @@
"babel-plugin-transform-runtime": "^6.23.0",
"babel-plugin-transform-strict-mode": "^6.18.0",
"babel-register": "^6.26.0",
"camelcase": "^5.0.0",
"clean-webpack-plugin": "^0.1.19",
"css-loader": "^0.28.11",
"extract-text-webpack-plugin": "^4.0.0-beta.0",

View File

@ -70,18 +70,19 @@
import Navigation from '../core/Navigation';
import ProgressBar from './components/ProgressBar';
import { mapState } from 'vuex';
import VueSocketio from 'vue-socket.io-extended';
import io from 'socket.io-client';
import Vue from 'vue';
import { Socketio } from './../../mixins/socketio';
import PowerButtons from './components/PowerButtons';
import Flash from '../Flash';
export default {
name: 'server',
mixins: [Socketio],
components: {
Flash,
PowerButtons, ProgressBar, Navigation,
TerminalIcon, FolderIcon, UsersIcon, CalendarIcon, DatabaseIcon, GlobeIcon, SettingsIcon
TerminalIcon, FolderIcon, UsersIcon, CalendarIcon, DatabaseIcon, GlobeIcon, SettingsIcon,
},
computed: {
@ -95,10 +96,20 @@
};
},
sockets: {
'console': function () {
console.log('server CONSOLE');
},
},
mounted: function () {
this.loadServer();
},
beforeDestroy: function () {
this.removeSocket();
},
methods: {
/**
* Load the core server information needed for these pages to be functional.
@ -115,7 +126,7 @@
query: `token=${this.credentials.key}`,
});
Vue.use(VueSocketio, socket, { store: this.$store });
this.$socket().connect(socket);
this.loadingServerData = false;
})
.catch(console.error);

View File

@ -24,11 +24,12 @@
<script>
import Status from './../../../helpers/statuses';
import { Socketio } from './../../../mixins/socketio';
import { mapState } from 'vuex';
export default {
name: 'power-buttons',
mixins: [Socketio],
computed: {
...mapState('socket', ['connected', 'status']),
},
@ -41,7 +42,7 @@
methods: {
sendPowerAction: function (action) {
this.$socket.emit('set status', action)
this.$socket().instance().emit('set status', action)
},
},
};

View File

@ -28,10 +28,12 @@
import { Terminal } from 'xterm';
import * as TerminalFit from 'xterm/lib/addons/fit/fit';
import {mapState} from 'vuex';
import {Socketio} from './../../../mixins/socketio';
Terminal.applyAddon(TerminalFit);
export default {
mixins: [Socketio],
name: 'console-page',
computed: {
...mapState('socket', ['connected']),
@ -103,7 +105,7 @@
this.terminal.fit();
this.terminal.clear();
this.$socket.emit('send server log');
this.$socket().instance().emit('send server log');
},
/**
@ -112,7 +114,7 @@
sendCommand: function () {
this.commandHistoryIndex = -1;
this.commandHistory.unshift(this.command);
this.$socket.emit('send command', this.command);
this.$socket().instance().emit('send command', this.command);
this.command = '';
},

View 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;
}
}

View 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);
});
}
}

View 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;
},
},
};

View File

@ -6880,12 +6880,6 @@ 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"