Handle connecting to websocket instance
Very beta code for handling sockets
This commit is contained in:
parent
6618a124e7
commit
f0ca8bc3a3
|
@ -21,9 +21,12 @@
|
||||||
"react-router-dom": "^5.0.1",
|
"react-router-dom": "^5.0.1",
|
||||||
"react-transition-group": "^4.1.0",
|
"react-transition-group": "^4.1.0",
|
||||||
"socket.io-client": "^2.2.0",
|
"socket.io-client": "^2.2.0",
|
||||||
|
"sockette": "^2.0.6",
|
||||||
"use-react-router": "^1.0.7",
|
"use-react-router": "^1.0.7",
|
||||||
"ws-wrapper": "^2.0.0",
|
"ws-wrapper": "^2.0.0",
|
||||||
"xterm": "^3.5.1",
|
"xterm": "^3.14.4",
|
||||||
|
"xterm-addon-attach": "^0.1.0",
|
||||||
|
"xterm-addon-fit": "^0.1.0",
|
||||||
"yup": "^0.27.0"
|
"yup": "^0.27.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import http from '@/api/http';
|
||||||
|
|
||||||
|
export interface Allocation {
|
||||||
|
ip: string;
|
||||||
|
alias: string | null;
|
||||||
|
port: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Server {
|
||||||
|
id: string;
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
node: string;
|
||||||
|
description: string;
|
||||||
|
allocations: Allocation[];
|
||||||
|
limits: {
|
||||||
|
memory: number;
|
||||||
|
swap: number;
|
||||||
|
disk: number;
|
||||||
|
io: number;
|
||||||
|
cpu: number;
|
||||||
|
};
|
||||||
|
featureLimits: {
|
||||||
|
databases: number;
|
||||||
|
allocations: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const rawDataToServerObject = (data: any): Server => ({
|
||||||
|
id: data.identifier,
|
||||||
|
uuid: data.uuid,
|
||||||
|
name: data.name,
|
||||||
|
node: data.node,
|
||||||
|
description: data.description ? ((data.description.length > 0) ? data.description : null) : null,
|
||||||
|
allocations: [{
|
||||||
|
ip: data.allocation.ip,
|
||||||
|
alias: null,
|
||||||
|
port: data.allocation.port,
|
||||||
|
}],
|
||||||
|
limits: { ...data.limits },
|
||||||
|
featureLimits: { ...data.feature_limits },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default (uuid: string): Promise<Server> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
http.get(`/api/client/servers/${uuid}`)
|
||||||
|
.then(response => resolve(rawDataToServerObject(response.data.attributes)))
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
|
@ -9,7 +9,7 @@ import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
export default () => (
|
export default () => (
|
||||||
<div className={'my-10'}>
|
<div className={'my-10'}>
|
||||||
<Link to={'/server/123'} className={'flex no-underline text-neutral-200 cursor-pointer items-center bg-neutral-700 p-4 border border-transparent hover:border-neutral-500'}>
|
<Link to={'/server/e9d6c836'} className={'flex no-underline text-neutral-200 cursor-pointer items-center bg-neutral-700 p-4 border border-transparent hover:border-neutral-500'}>
|
||||||
<div className={'rounded-full bg-neutral-500 p-3'}>
|
<div className={'rounded-full bg-neutral-500 p-3'}>
|
||||||
<FontAwesomeIcon icon={faServer}/>
|
<FontAwesomeIcon icon={faServer}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
export default ({ large }: { large?: boolean }) => (
|
||||||
|
<div className={classNames('spinner-circle spinner-white', { 'spinner-lg': large })}/>
|
||||||
|
);
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { CSSTransition } from 'react-transition-group';
|
import { CSSTransition } from 'react-transition-group';
|
||||||
|
import Spinner from '@/components/elements/Spinner';
|
||||||
|
|
||||||
export default ({ large, visible }: { visible: boolean; large?: boolean }) => (
|
export default ({ large, visible }: { visible: boolean; large?: boolean }) => (
|
||||||
<CSSTransition timeout={150} classNames={'fade'} in={visible} unmountOnExit={true}>
|
<CSSTransition timeout={150} classNames={'fade'} in={visible} unmountOnExit={true}>
|
||||||
|
@ -8,7 +9,7 @@ export default ({ large, visible }: { visible: boolean; large?: boolean }) => (
|
||||||
className={classNames('absolute pin-t pin-l flex items-center justify-center w-full h-full rounded')}
|
className={classNames('absolute pin-t pin-l flex items-center justify-center w-full h-full rounded')}
|
||||||
style={{ background: 'rgba(0, 0, 0, 0.45)' }}
|
style={{ background: 'rgba(0, 0, 0, 0.45)' }}
|
||||||
>
|
>
|
||||||
<div className={classNames('spinner-circle spinner-white', { 'spinner-lg': large })}></div>
|
<Spinner large={large}/>
|
||||||
</div>
|
</div>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React, { createRef, useEffect, useRef } from 'react';
|
||||||
|
import { Terminal } from 'xterm';
|
||||||
|
import * as TerminalFit from 'xterm/lib/addons/fit/fit';
|
||||||
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
|
|
||||||
|
const 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',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const ref = createRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
const terminal = useRef(new Terminal({
|
||||||
|
disableStdin: true,
|
||||||
|
cursorStyle: 'underline',
|
||||||
|
allowTransparency: true,
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'Menlo, Monaco, Consolas, monospace',
|
||||||
|
rows: 30,
|
||||||
|
theme: theme,
|
||||||
|
}));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current && terminal.current.open(ref.current);
|
||||||
|
|
||||||
|
// @see https://github.com/xtermjs/xterm.js/issues/2265
|
||||||
|
// @see https://github.com/xtermjs/xterm.js/issues/2230
|
||||||
|
TerminalFit.fit(terminal.current);
|
||||||
|
|
||||||
|
terminal.current.writeln('Testing console data');
|
||||||
|
terminal.current.writeln('Testing other data');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'text-xs font-mono relative'}>
|
||||||
|
<SpinnerOverlay visible={true} large={true}/>
|
||||||
|
<div
|
||||||
|
className={'rounded-t p-2 bg-black overflow-scroll w-full'}
|
||||||
|
style={{
|
||||||
|
minHeight: '16rem',
|
||||||
|
maxHeight: '64rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div id={'terminal'} ref={ref}/>
|
||||||
|
</div>
|
||||||
|
<div className={'rounded-b bg-neutral-900 text-neutral-100 flex'}>
|
||||||
|
<div className={'flex-no-shrink p-2 font-bold'}>$</div>
|
||||||
|
<div className={'w-full'}>
|
||||||
|
<input type={'text'} className={'bg-transparent text-neutral-100 p-2 pl-0 w-full'}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,7 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Console from '@/components/server/Console';
|
||||||
|
|
||||||
export default () => (
|
export default () => (
|
||||||
<div className={'my-10'}>
|
<div className={'my-10 flex'}>
|
||||||
Test
|
<div className={'mx-4 w-3/4 mr-4'}>
|
||||||
|
<Console/>
|
||||||
|
</div>
|
||||||
|
<div className={'flex-1 ml-4'}>
|
||||||
|
<p>Testing</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy';
|
||||||
|
import { ApplicationState } from '@/state/types';
|
||||||
|
import Sockette from 'sockette';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const server = useStoreState((state: State<ApplicationState>) => state.server.data);
|
||||||
|
const instance = useStoreState((state: State<ApplicationState>) => state.server.socket.instance);
|
||||||
|
const setInstance = useStoreActions((actions: Actions<ApplicationState>) => actions.server.socket.setInstance);
|
||||||
|
const setConnectionState = useStoreActions((actions: Actions<ApplicationState>) => actions.server.socket.setConnectionState);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// If there is already an instance or there is no server, just exit out of this process
|
||||||
|
// since we don't need to make a new connection.
|
||||||
|
if (instance || !server) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('need to connect to instance');
|
||||||
|
const socket = new Sockette(`wss://wings.pterodactyl.test:8080/api/servers/${server.uuid}/ws`, {
|
||||||
|
protocols: 'CC8kHCuMkXPosgzGO6d37wvhNcksWxG6kTrA',
|
||||||
|
// onmessage: (ev) => console.log(ev),
|
||||||
|
onopen: () => setConnectionState(true),
|
||||||
|
onclose: () => setConnectionState(false),
|
||||||
|
onerror: () => setConnectionState(false),
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Setting instance!');
|
||||||
|
|
||||||
|
setInstance(socket);
|
||||||
|
}, [server]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
|
@ -1,28 +1,52 @@
|
||||||
import * as React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||||
import NavigationBar from '@/components/NavigationBar';
|
import NavigationBar from '@/components/NavigationBar';
|
||||||
import ServerConsole from '@/components/server/ServerConsole';
|
import ServerConsole from '@/components/server/ServerConsole';
|
||||||
import TransitionRouter from '@/TransitionRouter';
|
import TransitionRouter from '@/TransitionRouter';
|
||||||
|
import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy';
|
||||||
|
import { ApplicationState } from '@/state/types';
|
||||||
|
import Spinner from '@/components/elements/Spinner';
|
||||||
|
import WebsocketHandler from '@/components/server/WebsocketHandler';
|
||||||
|
|
||||||
export default ({ match, location }: RouteComponentProps) => (
|
export default ({ match, location }: RouteComponentProps<{ id: string }>) => {
|
||||||
<React.Fragment>
|
const server = useStoreState((state: State<ApplicationState>) => state.server.data);
|
||||||
<NavigationBar/>
|
const { clearServerState, getServer } = useStoreActions((actions: Actions<ApplicationState>) => actions.server);
|
||||||
<div id={'sub-navigation'}>
|
|
||||||
<div className={'mx-auto'} style={{ maxWidth: '1200px' }}>
|
if (!server) {
|
||||||
<div className={'items'}>
|
getServer(match.params.id);
|
||||||
<NavLink to={`${match.url}`} exact>Console</NavLink>
|
}
|
||||||
<NavLink to={`${match.url}/files`}>File Manager</NavLink>
|
|
||||||
<NavLink to={`${match.url}/databases`}>Databases</NavLink>
|
useEffect(() => () => clearServerState(), []);
|
||||||
<NavLink to={`${match.url}/users`}>User Management</NavLink>
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<NavigationBar/>
|
||||||
|
<div id={'sub-navigation'}>
|
||||||
|
<div className={'mx-auto'} style={{ maxWidth: '1200px' }}>
|
||||||
|
<div className={'items'}>
|
||||||
|
<NavLink to={`${match.url}`} exact>Console</NavLink>
|
||||||
|
<NavLink to={`${match.url}/files`}>File Manager</NavLink>
|
||||||
|
<NavLink to={`${match.url}/databases`}>Databases</NavLink>
|
||||||
|
<NavLink to={`${match.url}/users`}>User Management</NavLink>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<TransitionRouter>
|
||||||
<TransitionRouter>
|
<div className={'w-full mx-auto'} style={{ maxWidth: '1200px' }}>
|
||||||
<div className={'w-full mx-auto'} style={{ maxWidth: '1200px' }}>
|
{!server ?
|
||||||
<Switch location={location}>
|
<div className={'flex justify-center m-20'}>
|
||||||
<Route path={`${match.path}`} component={ServerConsole} exact/>
|
<Spinner large={true}/>
|
||||||
</Switch>
|
</div>
|
||||||
</div>
|
:
|
||||||
</TransitionRouter>
|
<React.Fragment>
|
||||||
</React.Fragment>
|
<WebsocketHandler/>
|
||||||
);
|
<Switch location={location}>
|
||||||
|
<Route path={`${match.path}`} component={ServerConsole} exact/>
|
||||||
|
</Switch>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</TransitionRouter>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -2,10 +2,12 @@ import { createStore } from 'easy-peasy';
|
||||||
import { ApplicationState } from '@/state/types';
|
import { ApplicationState } from '@/state/types';
|
||||||
import flashes from '@/state/models/flashes';
|
import flashes from '@/state/models/flashes';
|
||||||
import user from '@/state/models/user';
|
import user from '@/state/models/user';
|
||||||
|
import server from '@/state/models/server';
|
||||||
|
|
||||||
const state: ApplicationState = {
|
const state: ApplicationState = {
|
||||||
flashes,
|
flashes,
|
||||||
user,
|
user,
|
||||||
|
server,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const store = createStore(state);
|
export const store = createStore(state);
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import getServer, { Server } from '@/api/server/getServer';
|
||||||
|
import { action, Action, thunk, Thunk } from 'easy-peasy';
|
||||||
|
import socket, { SocketState } from './socket';
|
||||||
|
|
||||||
|
export interface ServerState {
|
||||||
|
data?: Server;
|
||||||
|
socket: SocketState;
|
||||||
|
getServer: Thunk<ServerState, string, {}, any, Promise<void>>;
|
||||||
|
setServer: Action<ServerState, Server>;
|
||||||
|
clearServerState: Action<ServerState>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const server: ServerState = {
|
||||||
|
socket,
|
||||||
|
getServer: thunk(async (actions, payload) => {
|
||||||
|
const server = await getServer(payload);
|
||||||
|
actions.setServer(server);
|
||||||
|
}),
|
||||||
|
setServer: action((state, payload) => {
|
||||||
|
state.data = payload;
|
||||||
|
}),
|
||||||
|
clearServerState: action(state => {
|
||||||
|
state.data = undefined;
|
||||||
|
|
||||||
|
if (state.socket.instance) {
|
||||||
|
state.socket.instance.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.socket.instance = null;
|
||||||
|
state.socket.connected = false;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default server;
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Action, action } from 'easy-peasy';
|
||||||
|
import Sockette from 'sockette';
|
||||||
|
|
||||||
|
export interface SocketState {
|
||||||
|
instance: Sockette | null;
|
||||||
|
connected: boolean;
|
||||||
|
setInstance: Action<SocketState, Sockette | null>;
|
||||||
|
setConnectionState: Action<SocketState, boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const socket: SocketState = {
|
||||||
|
instance: null,
|
||||||
|
connected: false,
|
||||||
|
setInstance: action((state, payload) => {
|
||||||
|
state.instance = payload;
|
||||||
|
}),
|
||||||
|
setConnectionState: action((state, payload) => {
|
||||||
|
state.connected = payload;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default socket;
|
|
@ -1,10 +1,12 @@
|
||||||
import { FlashMessageType } from '@/components/MessageBox';
|
import { FlashMessageType } from '@/components/MessageBox';
|
||||||
import { Action } from 'easy-peasy';
|
import { Action } from 'easy-peasy';
|
||||||
import { UserState } from '@/state/models/user';
|
import { UserState } from '@/state/models/user';
|
||||||
|
import { ServerState } from '@/state/models/server';
|
||||||
|
|
||||||
export interface ApplicationState {
|
export interface ApplicationState {
|
||||||
flashes: FlashState;
|
flashes: FlashState;
|
||||||
user: UserState;
|
user: UserState;
|
||||||
|
server: ServerState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FlashState {
|
export interface FlashState {
|
||||||
|
|
|
@ -49,7 +49,7 @@ module.exports = {
|
||||||
cache: true,
|
cache: true,
|
||||||
target: 'web',
|
target: 'web',
|
||||||
mode: process.env.NODE_ENV,
|
mode: process.env.NODE_ENV,
|
||||||
devtool: isProduction ? false : 'cheap-eval-source-map',
|
devtool: isProduction ? false : 'eval-source-map',
|
||||||
performance: {
|
performance: {
|
||||||
hints: false,
|
hints: false,
|
||||||
},
|
},
|
||||||
|
|
18
yarn.lock
18
yarn.lock
|
@ -6925,6 +6925,10 @@ socket.io-parser@~3.3.0:
|
||||||
debug "~3.1.0"
|
debug "~3.1.0"
|
||||||
isarray "2.0.1"
|
isarray "2.0.1"
|
||||||
|
|
||||||
|
sockette@^2.0.6:
|
||||||
|
version "2.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/sockette/-/sockette-2.0.6.tgz#63b533f3cfe3b592fc84178beea6577fa18cebf3"
|
||||||
|
|
||||||
sockjs-client@1.3.0:
|
sockjs-client@1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.3.0.tgz#12fc9d6cb663da5739d3dc5fb6e8687da95cb177"
|
resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.3.0.tgz#12fc9d6cb663da5739d3dc5fb6e8687da95cb177"
|
||||||
|
@ -8015,9 +8019,17 @@ xtend@^4.0.0, xtend@~4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||||
|
|
||||||
xterm@^3.5.1:
|
xterm-addon-attach@^0.1.0:
|
||||||
version "3.5.1"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.5.1.tgz#d2e62ab26108a771b7bd1b7be4f6578fb4aff922"
|
resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.1.0.tgz#e0daa8188e9bb830def9ccad015fc62bc07e3abe"
|
||||||
|
|
||||||
|
xterm-addon-fit@^0.1.0:
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.1.0.tgz#dd52d8b2ec6ef05faab8285bafd9310063704468"
|
||||||
|
|
||||||
|
xterm@^3.14.4:
|
||||||
|
version "3.14.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.14.4.tgz#68a474fd0628e6027e420f6c8b0df136f6281ff8"
|
||||||
|
|
||||||
y18n@^3.2.1:
|
y18n@^3.2.1:
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
|
|
Loading…
Reference in New Issue