test connection UI (#4596)

* test connection UI

* cleanup

Co-authored-by: Jeremy Letto <jeremy.letto@datasite.com>
This commit is contained in:
Jeremy Letto 2022-03-19 19:22:00 -05:00 committed by GitHub
parent 00a2a8ab71
commit 0ff59e6d1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 169 additions and 22 deletions

View file

@ -6,6 +6,10 @@ export default class AuthenticationService {
SessionCommands.connect(options, WebSocketConnectReason.LOGIN); SessionCommands.connect(options, WebSocketConnectReason.LOGIN);
} }
static testConnection(options: WebSocketConnectOptions): void {
SessionCommands.connect(options, WebSocketConnectReason.TEST_CONNECTION);
}
static register(options: WebSocketConnectOptions): void { static register(options: WebSocketConnectOptions): void {
SessionCommands.connect(options, WebSocketConnectReason.REGISTER); SessionCommands.connect(options, WebSocketConnectReason.REGISTER);
} }

View file

@ -1,8 +1,23 @@
.KnownHosts { .KnownHosts {
}
.KnownHosts-form {
width: 100%; width: 100%;
position: relative; position: relative;
} }
.KnownHosts-item {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.KnownHosts-item__wrapper {
display: flex;
align-items: center;
}
.KnownHosts-item__label { .KnownHosts-item__label {
position: relative; position: relative;
} }
@ -16,6 +31,16 @@
font-size: .9em; font-size: .9em;
} }
.KnownHosts-item__status {
display: none;
}
.KnownHosts-item__status svg {
display: none;
margin-left: -5px;
margin-right: 5px;
}
.KnownHosts-validation { .KnownHosts-validation {
position: absolute; position: absolute;
top: 0; top: 0;
@ -33,6 +58,11 @@
margin-left: 4px; margin-left: 4px;
} }
.KnownHosts .MuiSelect-selectMenu .KnownHosts-item__status,
.KnownHosts .MuiSelect-selectMenu .KnownHosts-item__status svg {
display: block;
}
.Mui-selected .KnownHosts-item__label svg { .Mui-selected .KnownHosts-item__label svg {
display: block; display: block;
} }

View file

@ -4,6 +4,8 @@ import { Select, MenuItem } from '@material-ui/core';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import FormControl from '@material-ui/core/FormControl'; import FormControl from '@material-ui/core/FormControl';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
import WifiTetheringIcon from '@material-ui/icons/WifiTethering';
import PortableWifiOffIcon from '@material-ui/icons/PortableWifiOff';
import InputLabel from '@material-ui/core/InputLabel'; import InputLabel from '@material-ui/core/InputLabel';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import Check from '@material-ui/icons/Check'; import Check from '@material-ui/icons/Check';
@ -11,13 +13,22 @@ import AddIcon from '@material-ui/icons/Add';
import EditRoundedIcon from '@material-ui/icons/Edit'; import EditRoundedIcon from '@material-ui/icons/Edit';
import ErrorOutlinedIcon from '@material-ui/icons/ErrorOutlined'; import ErrorOutlinedIcon from '@material-ui/icons/ErrorOutlined';
import { AuthenticationService } from 'api';
import { KnownHostDialog } from 'dialogs'; import { KnownHostDialog } from 'dialogs';
import { useReduxEffect } from 'hooks';
import { HostDTO } from 'services'; import { HostDTO } from 'services';
import { ServerTypes } from 'store';
import { DefaultHosts, Host, getHostPort } from 'types'; import { DefaultHosts, Host, getHostPort } from 'types';
import Toast from 'components/Toast/Toast'; import Toast from 'components/Toast/Toast';
import './KnownHosts.css'; import './KnownHosts.css';
enum TestConnection {
TESTING = 'testing',
FAILED = 'failed',
SUCCESS = 'success',
}
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
root: { root: {
'& .KnownHosts-error': { '& .KnownHosts-error': {
@ -26,6 +37,18 @@ const useStyles = makeStyles(theme => ({
'& .KnownHosts-warning': { '& .KnownHosts-warning': {
color: theme.palette.warning.main color: theme.palette.warning.main
},
'& .KnownHosts-item': {
[`& .${TestConnection.TESTING}`]: {
color: theme.palette.warning.main
},
[`& .${TestConnection.FAILED}`]: {
color: theme.palette.error.main
},
[`& .${TestConnection.SUCCESS}`]: {
color: theme.palette.success.main
}
} }
}, },
})); }));
@ -46,9 +69,11 @@ const KnownHosts = (props) => {
edit: null, edit: null,
}); });
const [showCreateToast, setShowCreateToast] = useState(false) const [testingConnection, setTestingConnection] = useState<TestConnection>(null);
const [showDeleteToast, setShowDeleteToast] = useState(false)
const [showEditToast, setShowEditToast] = useState(false) const [showCreateToast, setShowCreateToast] = useState(false);
const [showDeleteToast, setShowDeleteToast] = useState(false);
const [showEditToast, setShowEditToast] = useState(false);
const loadKnownHosts = useCallback(async () => { const loadKnownHosts = useCallback(async () => {
const hosts = await HostDTO.getAll(); const hosts = await HostDTO.getAll();
@ -77,6 +102,14 @@ const KnownHosts = (props) => {
} }
}, [hostsState, onChange]); }, [hostsState, onChange]);
useReduxEffect(() => {
setTestingConnection(TestConnection.SUCCESS);
}, ServerTypes.TEST_CONNECTION_SUCCESSFUL, []);
useReduxEffect(() => {
setTestingConnection(TestConnection.FAILED);
}, ServerTypes.TEST_CONNECTION_FAILED, []);
const selectHost = (selectedHost) => { const selectHost = (selectedHost) => {
setHostsState(s => ({ ...s, selectedHost })); setHostsState(s => ({ ...s, selectedHost }));
}; };
@ -135,6 +168,8 @@ const KnownHosts = (props) => {
}; };
const updateLastSelectedHost = (hostId): Promise<any[]> => { const updateLastSelectedHost = (hostId): Promise<any[]> => {
testConnection();
return HostDTO.getAll().then(hosts => return HostDTO.getAll().then(hosts =>
hosts.map(async host => { hosts.map(async host => {
if (host.id === hostId) { if (host.id === hostId) {
@ -152,20 +187,27 @@ const KnownHosts = (props) => {
); );
}; };
const testConnection = () => {
setTestingConnection(TestConnection.TESTING);
const options = { ...getHostPort(hostsState.selectedHost) };
AuthenticationService.testConnection(options);
}
return ( return (
<div> <div className={'KnownHosts ' + classes.root}>
<FormControl variant='outlined' className={'KnownHosts ' + classes.root}> <FormControl variant='outlined' className='KnownHosts-form'>
{ touched && ( { touched && (
<div className="KnownHosts-validation"> <div className='KnownHosts-validation'>
{ {
(error && (error &&
<div className="KnownHosts-error"> <div className='KnownHosts-error'>
{error} {error}
<ErrorOutlinedIcon style={{ fontSize: 'small', fontWeight: 'bold' }} /> <ErrorOutlinedIcon style={{ fontSize: 'small', fontWeight: 'bold' }} />
</div> </div>
) || ) ||
(warning && <div className="KnownHosts-warning">{warning}</div>) (warning && <div className='KnownHosts-warning'>{warning}</div>)
} }
</div> </div>
) } ) }
@ -189,19 +231,33 @@ const KnownHosts = (props) => {
{ {
hostsState.hosts.map((host, index) => ( hostsState.hosts.map((host, index) => (
<MenuItem className='KnownHosts-item' value={host} key={index}> <MenuItem value={host} key={index}>
<div className='KnownHosts-item__label'> <div className='KnownHosts-item'>
<Check /> <div className='KnownHosts-item__wrapper'>
<span>{host.name} ({ getHostPort(hostsState.hosts[index]).host }:{getHostPort(hostsState.hosts[index]).port})</span> <div className='KnownHosts-item__status'>
</div> <div className={testingConnection}>
{
testingConnection === TestConnection.FAILED
? <PortableWifiOffIcon fontSize="small" />
: <WifiTetheringIcon fontSize="small" />
}
</div>
</div>
{ host.editable && ( <div className='KnownHosts-item__label'>
<IconButton className='KnownHosts-item__edit' size='small' color='primary' onClick={(e) => { <Check />
openEditKnownHostDialog(hostsState.hosts[index]); <span>{host.name} ({ getHostPort(hostsState.hosts[index]).host }:{getHostPort(hostsState.hosts[index]).port})</span>
}}> </div>
<EditRoundedIcon fontSize='small' /> </div>
</IconButton>
) } { host.editable && (
<IconButton className='KnownHosts-item__edit' size='small' color='primary' onClick={(e) => {
openEditKnownHostDialog(hostsState.hosts[index]);
}}>
<EditRoundedIcon fontSize='small' />
</IconButton>
) }
</div>
</MenuItem> </MenuItem>
)) ))
} }

View file

@ -24,6 +24,12 @@ export const Actions = {
connectionFailed: () => ({ connectionFailed: () => ({
type: Types.CONNECTION_FAILED, type: Types.CONNECTION_FAILED,
}), }),
testConnectionSuccessful: () => ({
type: Types.TEST_CONNECTION_SUCCESSFUL,
}),
testConnectionFailed: () => ({
type: Types.TEST_CONNECTION_FAILED,
}),
serverMessage: message => ({ serverMessage: message => ({
type: Types.SERVER_MESSAGE, type: Types.SERVER_MESSAGE,
message message

View file

@ -22,6 +22,12 @@ export const Dispatch = {
connectionFailed: () => { connectionFailed: () => {
store.dispatch(Actions.connectionFailed()); store.dispatch(Actions.connectionFailed());
}, },
testConnectionSuccessful: () => {
store.dispatch(Actions.testConnectionSuccessful());
},
testConnectionFailed: () => {
store.dispatch(Actions.testConnectionFailed());
},
updateBuddyList: buddyList => { updateBuddyList: buddyList => {
store.dispatch(Actions.updateBuddyList(buddyList)); store.dispatch(Actions.updateBuddyList(buddyList));
}, },

View file

@ -5,6 +5,8 @@ export const Types = {
LOGIN_FAILED: '[Server] Login Failed', LOGIN_FAILED: '[Server] Login Failed',
CONNECTION_CLOSED: '[Server] Connection Closed', CONNECTION_CLOSED: '[Server] Connection Closed',
CONNECTION_FAILED: '[Server] Connection Failed', CONNECTION_FAILED: '[Server] Connection Failed',
TEST_CONNECTION_SUCCESSFUL: '[Server] Test Connection Successful',
TEST_CONNECTION_FAILED: '[Server] Test Connection Failed',
SERVER_MESSAGE: '[Server] Server Message', SERVER_MESSAGE: '[Server] Server Message',
UPDATE_BUDDY_LIST: '[Server] Update Buddy List', UPDATE_BUDDY_LIST: '[Server] Update Buddy List',
ADD_TO_BUDDY_LIST: '[Server] Add to Buddy List', ADD_TO_BUDDY_LIST: '[Server] Add to Buddy List',

View file

@ -35,7 +35,8 @@ export enum WebSocketConnectReason {
ACTIVATE_ACCOUNT, ACTIVATE_ACCOUNT,
PASSWORD_RESET_REQUEST, PASSWORD_RESET_REQUEST,
PASSWORD_RESET_CHALLENGE, PASSWORD_RESET_CHALLENGE,
PASSWORD_RESET PASSWORD_RESET,
TEST_CONNECTION,
} }
export class Host { export class Host {

View file

@ -57,7 +57,11 @@ export class WebClient {
public connect(options: WebSocketConnectOptions) { public connect(options: WebSocketConnectOptions) {
this.connectionAttemptMade = true; this.connectionAttemptMade = true;
this.options = options; this.options = options;
this.socket.connect(this.options); this.socket.connect(options);
}
public testConnect(options: WebSocketConnectOptions) {
this.socket.testConnect(options);
} }
public disconnect() { public disconnect() {

View file

@ -25,6 +25,9 @@ export class SessionCommands {
case WebSocketConnectReason.PASSWORD_RESET: case WebSocketConnectReason.PASSWORD_RESET:
SessionCommands.updateStatus(StatusEnum.CONNECTING, 'Connecting...'); SessionCommands.updateStatus(StatusEnum.CONNECTING, 'Connecting...');
break; break;
case WebSocketConnectReason.TEST_CONNECTION:
webClient.testConnect({ ...options });
return;
default: default:
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Unknown Connection Attempt: ' + reason); SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Unknown Connection Attempt: ' + reason);
return; return;

View file

@ -29,6 +29,14 @@ export class SessionPersistence {
ServerDispatch.connectionFailed(); ServerDispatch.connectionFailed();
} }
static testConnectionSuccessful() {
ServerDispatch.testConnectionSuccessful();
}
static testConnectionFailed() {
ServerDispatch.testConnectionFailed();
}
static updateBuddyList(buddyList) { static updateBuddyList(buddyList) {
ServerDispatch.updateBuddyList(buddyList); ServerDispatch.updateBuddyList(buddyList);
} }

View file

@ -38,6 +38,16 @@ export class WebSocketService {
this.socket = this.createWebSocket(`${protocol}://${host}:${port}`); this.socket = this.createWebSocket(`${protocol}://${host}:${port}`);
} }
public testConnect(options: WebSocketConnectOptions, protocol: string = 'wss'): void {
if (window.location.hostname === 'localhost') {
protocol = 'ws';
}
const { host, port } = options;
this.testWebSocket(`${protocol}://${host}:${port}`);
}
public disconnect(): void { public disconnect(): void {
if (this.socket) { if (this.socket) {
this.socket.close(); this.socket.close();
@ -92,4 +102,21 @@ export class WebSocketService {
return socket; return socket;
} }
private testWebSocket(url: string): void {
const socket = new WebSocket(url);
socket.binaryType = 'arraybuffer';
const connectionTimer = setTimeout(() => socket.close(), this.webClient.clientOptions.keepalive);
socket.onopen = () => {
clearTimeout(connectionTimer);
SessionPersistence.testConnectionSuccessful();
socket.close();
};
socket.onerror = () => {
SessionPersistence.testConnectionFailed();
};
}
} }