Skip to content

Commit f0ca8bc

Browse files
committed
Handle connecting to websocket instance
Very beta code for handling sockets
1 parent 6618a12 commit f0ca8bc

File tree

15 files changed

+297
-30
lines changed

15 files changed

+297
-30
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121
"react-router-dom": "^5.0.1",
2222
"react-transition-group": "^4.1.0",
2323
"socket.io-client": "^2.2.0",
24+
"sockette": "^2.0.6",
2425
"use-react-router": "^1.0.7",
2526
"ws-wrapper": "^2.0.0",
26-
"xterm": "^3.5.1",
27+
"xterm": "^3.14.4",
28+
"xterm-addon-attach": "^0.1.0",
29+
"xterm-addon-fit": "^0.1.0",
2730
"yup": "^0.27.0"
2831
},
2932
"devDependencies": {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import http from '@/api/http';
2+
3+
export interface Allocation {
4+
ip: string;
5+
alias: string | null;
6+
port: number;
7+
}
8+
9+
export interface Server {
10+
id: string;
11+
uuid: string;
12+
name: string;
13+
node: string;
14+
description: string;
15+
allocations: Allocation[];
16+
limits: {
17+
memory: number;
18+
swap: number;
19+
disk: number;
20+
io: number;
21+
cpu: number;
22+
};
23+
featureLimits: {
24+
databases: number;
25+
allocations: number;
26+
};
27+
}
28+
29+
export const rawDataToServerObject = (data: any): Server => ({
30+
id: data.identifier,
31+
uuid: data.uuid,
32+
name: data.name,
33+
node: data.node,
34+
description: data.description ? ((data.description.length > 0) ? data.description : null) : null,
35+
allocations: [{
36+
ip: data.allocation.ip,
37+
alias: null,
38+
port: data.allocation.port,
39+
}],
40+
limits: { ...data.limits },
41+
featureLimits: { ...data.feature_limits },
42+
});
43+
44+
export default (uuid: string): Promise<Server> => {
45+
return new Promise((resolve, reject) => {
46+
http.get(`/api/client/servers/${uuid}`)
47+
.then(response => resolve(rawDataToServerObject(response.data.attributes)))
48+
.catch(reject);
49+
});
50+
};

resources/scripts/components/dashboard/DashboardContainer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Link } from 'react-router-dom';
99

1010
export default () => (
1111
<div className={'my-10'}>
12-
<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'}>
12+
<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'}>
1313
<div className={'rounded-full bg-neutral-500 p-3'}>
1414
<FontAwesomeIcon icon={faServer}/>
1515
</div>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import React from 'react';
2+
import classNames from 'classnames';
3+
4+
export default ({ large }: { large?: boolean }) => (
5+
<div className={classNames('spinner-circle spinner-white', { 'spinner-lg': large })}/>
6+
);
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import React from 'react';
22
import classNames from 'classnames';
33
import { CSSTransition } from 'react-transition-group';
4+
import Spinner from '@/components/elements/Spinner';
45

56
export default ({ large, visible }: { visible: boolean; large?: boolean }) => (
67
<CSSTransition timeout={150} classNames={'fade'} in={visible} unmountOnExit={true}>
78
<div
89
className={classNames('absolute pin-t pin-l flex items-center justify-center w-full h-full rounded')}
910
style={{ background: 'rgba(0, 0, 0, 0.45)' }}
1011
>
11-
<div className={classNames('spinner-circle spinner-white', { 'spinner-lg': large })}></div>
12+
<Spinner large={large}/>
1213
</div>
1314
</CSSTransition>
1415
);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React, { createRef, useEffect, useRef } from 'react';
2+
import { Terminal } from 'xterm';
3+
import * as TerminalFit from 'xterm/lib/addons/fit/fit';
4+
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
5+
6+
const theme = {
7+
background: 'transparent',
8+
cursor: 'transparent',
9+
black: '#000000',
10+
red: '#E54B4B',
11+
green: '#9ECE58',
12+
yellow: '#FAED70',
13+
blue: '#396FE2',
14+
magenta: '#BB80B3',
15+
cyan: '#2DDAFD',
16+
white: '#d0d0d0',
17+
brightBlack: 'rgba(255, 255, 255, 0.2)',
18+
brightRed: '#FF5370',
19+
brightGreen: '#C3E88D',
20+
brightYellow: '#FFCB6B',
21+
brightBlue: '#82AAFF',
22+
brightMagenta: '#C792EA',
23+
brightCyan: '#89DDFF',
24+
brightWhite: '#ffffff',
25+
};
26+
27+
export default () => {
28+
const ref = createRef<HTMLDivElement>();
29+
30+
const terminal = useRef(new Terminal({
31+
disableStdin: true,
32+
cursorStyle: 'underline',
33+
allowTransparency: true,
34+
fontSize: 12,
35+
fontFamily: 'Menlo, Monaco, Consolas, monospace',
36+
rows: 30,
37+
theme: theme,
38+
}));
39+
40+
useEffect(() => {
41+
ref.current && terminal.current.open(ref.current);
42+
43+
// @see https://github.com/xtermjs/xterm.js/issues/2265
44+
// @see https://github.com/xtermjs/xterm.js/issues/2230
45+
TerminalFit.fit(terminal.current);
46+
47+
terminal.current.writeln('Testing console data');
48+
terminal.current.writeln('Testing other data');
49+
}, []);
50+
51+
return (
52+
<div className={'text-xs font-mono relative'}>
53+
<SpinnerOverlay visible={true} large={true}/>
54+
<div
55+
className={'rounded-t p-2 bg-black overflow-scroll w-full'}
56+
style={{
57+
minHeight: '16rem',
58+
maxHeight: '64rem',
59+
}}
60+
>
61+
<div id={'terminal'} ref={ref}/>
62+
</div>
63+
<div className={'rounded-b bg-neutral-900 text-neutral-100 flex'}>
64+
<div className={'flex-no-shrink p-2 font-bold'}>$</div>
65+
<div className={'w-full'}>
66+
<input type={'text'} className={'bg-transparent text-neutral-100 p-2 pl-0 w-full'}/>
67+
</div>
68+
</div>
69+
</div>
70+
);
71+
};
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import React from 'react';
2+
import Console from '@/components/server/Console';
23

34
export default () => (
4-
<div className={'my-10'}>
5-
Test
5+
<div className={'my-10 flex'}>
6+
<div className={'mx-4 w-3/4 mr-4'}>
7+
<Console/>
8+
</div>
9+
<div className={'flex-1 ml-4'}>
10+
<p>Testing</p>
11+
</div>
612
</div>
713
);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React, { useEffect } from 'react';
2+
import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy';
3+
import { ApplicationState } from '@/state/types';
4+
import Sockette from 'sockette';
5+
6+
export default () => {
7+
const server = useStoreState((state: State<ApplicationState>) => state.server.data);
8+
const instance = useStoreState((state: State<ApplicationState>) => state.server.socket.instance);
9+
const setInstance = useStoreActions((actions: Actions<ApplicationState>) => actions.server.socket.setInstance);
10+
const setConnectionState = useStoreActions((actions: Actions<ApplicationState>) => actions.server.socket.setConnectionState);
11+
12+
useEffect(() => {
13+
// If there is already an instance or there is no server, just exit out of this process
14+
// since we don't need to make a new connection.
15+
if (instance || !server) {
16+
return;
17+
}
18+
19+
console.log('need to connect to instance');
20+
const socket = new Sockette(`wss://wings.pterodactyl.test:8080/api/servers/${server.uuid}/ws`, {
21+
protocols: 'CC8kHCuMkXPosgzGO6d37wvhNcksWxG6kTrA',
22+
// onmessage: (ev) => console.log(ev),
23+
onopen: () => setConnectionState(true),
24+
onclose: () => setConnectionState(false),
25+
onerror: () => setConnectionState(false),
26+
});
27+
28+
console.log('Setting instance!');
29+
30+
setInstance(socket);
31+
}, [server]);
32+
33+
return null;
34+
};
Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,52 @@
1-
import * as React from 'react';
1+
import React, { useEffect } from 'react';
22
import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom';
33
import NavigationBar from '@/components/NavigationBar';
44
import ServerConsole from '@/components/server/ServerConsole';
55
import TransitionRouter from '@/TransitionRouter';
6+
import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy';
7+
import { ApplicationState } from '@/state/types';
8+
import Spinner from '@/components/elements/Spinner';
9+
import WebsocketHandler from '@/components/server/WebsocketHandler';
610

7-
export default ({ match, location }: RouteComponentProps) => (
8-
<React.Fragment>
9-
<NavigationBar/>
10-
<div id={'sub-navigation'}>
11-
<div className={'mx-auto'} style={{ maxWidth: '1200px' }}>
12-
<div className={'items'}>
13-
<NavLink to={`${match.url}`} exact>Console</NavLink>
14-
<NavLink to={`${match.url}/files`}>File Manager</NavLink>
15-
<NavLink to={`${match.url}/databases`}>Databases</NavLink>
16-
<NavLink to={`${match.url}/users`}>User Management</NavLink>
11+
export default ({ match, location }: RouteComponentProps<{ id: string }>) => {
12+
const server = useStoreState((state: State<ApplicationState>) => state.server.data);
13+
const { clearServerState, getServer } = useStoreActions((actions: Actions<ApplicationState>) => actions.server);
14+
15+
if (!server) {
16+
getServer(match.params.id);
17+
}
18+
19+
useEffect(() => () => clearServerState(), []);
20+
21+
return (
22+
<React.Fragment>
23+
<NavigationBar/>
24+
<div id={'sub-navigation'}>
25+
<div className={'mx-auto'} style={{ maxWidth: '1200px' }}>
26+
<div className={'items'}>
27+
<NavLink to={`${match.url}`} exact>Console</NavLink>
28+
<NavLink to={`${match.url}/files`}>File Manager</NavLink>
29+
<NavLink to={`${match.url}/databases`}>Databases</NavLink>
30+
<NavLink to={`${match.url}/users`}>User Management</NavLink>
31+
</div>
1732
</div>
1833
</div>
19-
</div>
20-
<TransitionRouter>
21-
<div className={'w-full mx-auto'} style={{ maxWidth: '1200px' }}>
22-
<Switch location={location}>
23-
<Route path={`${match.path}`} component={ServerConsole} exact/>
24-
</Switch>
25-
</div>
26-
</TransitionRouter>
27-
</React.Fragment>
28-
);
34+
<TransitionRouter>
35+
<div className={'w-full mx-auto'} style={{ maxWidth: '1200px' }}>
36+
{!server ?
37+
<div className={'flex justify-center m-20'}>
38+
<Spinner large={true}/>
39+
</div>
40+
:
41+
<React.Fragment>
42+
<WebsocketHandler/>
43+
<Switch location={location}>
44+
<Route path={`${match.path}`} component={ServerConsole} exact/>
45+
</Switch>
46+
</React.Fragment>
47+
}
48+
</div>
49+
</TransitionRouter>
50+
</React.Fragment>
51+
);
52+
};

resources/scripts/state/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { createStore } from 'easy-peasy';
22
import { ApplicationState } from '@/state/types';
33
import flashes from '@/state/models/flashes';
44
import user from '@/state/models/user';
5+
import server from '@/state/models/server';
56

67
const state: ApplicationState = {
78
flashes,
89
user,
10+
server,
911
};
1012

1113
export const store = createStore(state);

0 commit comments

Comments
 (0)