Use connection dropdown instead of modal for supplying SSH username

This commit is contained in:
Grzegorz Zdunek
2022-03-24 17:16:34 +01:00
committed by Grzegorz Zdunek
parent 4f8704c083
commit 2d07c288a4
12 changed files with 74 additions and 247 deletions
@@ -156,7 +156,7 @@ const Input = styled.input(
border: 1px solid ${theme.colors.subtle};
border-radius: 4px;
box-sizing: border-box;
color: #263238;
color: ${theme.colors.grey[900]};
height: 32px;
outline: none;
@@ -0,0 +1,21 @@
import React, { FC } from 'react';
import { ThemeProvider } from 'styled-components';
import theme from 'teleterm/ui/ThemeProvider/theme';
const menuSshLoginTheme = {
...theme,
colors: {
...theme.colors,
subtle:theme.colors.primary.lighter,
light: theme.colors.primary.dark,
grey: {
[50]: 'rgba(255,255,255,0.05)',
[900]: theme.colors.text.primary,
},
link: theme.colors.text.primary,
},
};
export const MenuSshLoginTheme: FC = props => (
<ThemeProvider theme={menuSshLoginTheme} children={props.children} />
);
@@ -18,8 +18,9 @@ import React from 'react';
import { useServers, State } from './useServers';
import * as types from 'teleterm/ui/services/clusters/types';
import Table, { Cell } from 'design/DataTable';
import { ButtonBorder } from 'design';
import { renderLabelCell } from '../renderLabelCell';
import MenuSshLogin from 'shared/components/MenuSshLogin';
import { MenuSshLoginTheme } from './MenuSshLoginTheme';
export default function Container() {
const state = useServers();
@@ -27,7 +28,7 @@ export default function Container() {
}
function ServerList(props: State) {
const { servers = [], connect } = props;
const { servers = [], getSshLogins, connect } = props;
return (
<Table
columns={[
@@ -49,7 +50,11 @@ function ServerList(props: State) {
},
{
altKey: 'connect-btn',
render: server => renderConnectCell(server.uri, connect),
render: server =>
renderConnectCell(
() => getSshLogins(server.uri),
login => connect(server.uri, login)
),
},
]}
emptyText="No Nodes Found"
@@ -60,19 +65,25 @@ function ServerList(props: State) {
}
const renderConnectCell = (
serverUri: string,
connect: (serverUri: string) => void
getSshLogins: () => string[],
onConnect: (login: string) => void
) => {
return (
<Cell align="right">
<ButtonBorder
size="small"
onClick={() => {
connect(serverUri);
}}
>
Connect
</ButtonBorder>
<MenuSshLoginTheme>
<MenuSshLogin
onOpen={() => getSshLogins().map(login => ({ login, url: '' }))}
onSelect={(e, login) => onConnect(login)}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
anchorOrigin={{
vertical: 'center',
horizontal: 'right',
}}
/>
</MenuSshLoginTheme>
</Cell>
);
};
@@ -87,4 +98,4 @@ const renderAddressCell = ({ addr, tunnel }: types.Server) => (
)}
{!tunnel && addr}
</Cell>
);
);
@@ -15,16 +15,39 @@ limitations under the License.
*/
import { useClusterContext } from 'teleterm/ui/DocumentCluster/clusterContext';
import { useAppContext } from 'teleterm/ui/appContextProvider';
export function useServers() {
const ctx = useClusterContext();
const servers = ctx.getServers();
const syncStatus = ctx.getSyncStatus().servers;
const appContext = useAppContext();
const clusterContext = useClusterContext();
const servers = clusterContext.getServers();
const syncStatus = clusterContext.getSyncStatus().servers;
function getSshLogins(serverUri: string): string[] {
const cluster = appContext.clustersService.findClusterByResource(serverUri);
return cluster?.loggedInUser?.sshLoginsList || [];
}
function connect(serverUri: string, login: string): void {
const server = appContext.clustersService.getServer(serverUri);
const rootCluster =
appContext.clustersService.findRootClusterByResource(serverUri);
const documentsService =
appContext.workspacesService.getWorkspaceDocumentService(rootCluster.uri);
const doc = documentsService.createTshNodeDocument(serverUri);
doc.title = `${login}@${server.hostname}`;
doc.login = login;
documentsService.add(doc);
documentsService.setLocation(doc.uri);
}
return {
connect: ctx.connectServer,
servers,
syncStatus,
getSshLogins,
connect,
};
}
@@ -71,10 +71,6 @@ class ClusterContext extends Store<State> {
this.appCtx.commandLauncher.executeCommand('kube-connect', { kubeUri });
};
connectServer = (serverUri: string) => {
this.appCtx.commandLauncher.executeCommand('ssh', { serverUri });
};
connectDb = (dbUri: string) => {
this.appCtx.commandLauncher.executeCommand('proxy-db', { dbUri });
};
@@ -17,7 +17,6 @@
import React from 'react';
import { useAppContext } from 'teleterm/ui/appContextProvider';
import GatewayCreate from 'teleterm/ui/GatewayCreate';
import ServerConnect from 'teleterm/ui/ServerConnect';
import ClusterRemove from '../ClusterRemove/ClusterRemove';
import { ClusterConnect } from 'teleterm/ui/ClusterConnect';
@@ -51,9 +50,5 @@ export default function ModalsHost() {
return <GatewayCreate {...dialog} onClose={handleClose} />;
}
if (dialog.kind === 'server-connect') {
return <ServerConnect serverUri={dialog.serverUri} onClose={handleClose} />;
}
return null;
}
@@ -1,44 +0,0 @@
/**
* Copyright 2020 Gravitational, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { Server } from 'teleterm/services/tshd/types';
import { ServerConnect } from './ServerConnect';
import { State } from './useServerConnect';
export default {
title: 'Teleterm/ServerConnect',
};
export const Story = () => {
const server: Server = {
uri: 'clusters/localhost/servers/hostname3',
tunnel: false,
name: 'server1',
hostname: 'hostname3',
addr: '123.12.12.12',
labelsList: [{ name: 'os', value: 'linux' }],
};
const props: State = {
server,
logins: ['a', 'test', 'test2', 'test3', 'test4', 'test5'],
connect: () => null,
onClose: () => null,
};
return <ServerConnect {...props} />;
};
@@ -1,82 +0,0 @@
/**
* Copyright 2021 Gravitational, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import Select, { Option, DarkStyledSelect } from 'shared/components/Select';
import { Text, ButtonSecondary } from 'design';
import Dialog, {
DialogHeader,
DialogContent,
DialogFooter,
} from 'design/Dialog';
import Validation from 'shared/components/Validation';
import useServerConnect, { State, Props } from './useServerConnect';
export default function Container(props: Props) {
const state = useServerConnect(props);
return <ServerConnect {...state} />;
}
export function ServerConnect(props: State) {
const { server, logins, onClose, connect } = props;
const loginOptions = logins.map(l => ({ value: l, label: l }));
const handleOnChange = (option: Option) => {
connect(option.value);
};
return (
<Validation>
<Dialog
dialogCss={() => ({
maxWidth: '600px',
width: '100%',
padding: '20px',
height: '260px',
})}
disableEscapeKeyDown={false}
onClose={onClose}
open={true}
>
<DialogHeader>
<Text typography="h4" color="text.primary">
Connect to <b>{server.hostname}</b>
</Text>
</DialogHeader>
<DialogContent>
<DarkStyledSelect>
<Select
placeholder="login as..."
minMenuHeight={128}
maxMenuHeight={128}
menuIsOpen={true}
autoFocus={true}
isSearchable={true}
value={null}
options={loginOptions}
onChange={handleOnChange}
components={{ DropdownIndicator }}
/>
</DarkStyledSelect>
</DialogContent>
<DialogFooter>
<ButtonSecondary onClick={onClose}>Close</ButtonSecondary>
</DialogFooter>
</Dialog>
</Validation>
);
}
const DropdownIndicator = () => null;
@@ -1,18 +0,0 @@
/*
Copyright 2021 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import ServerConnect from './ServerConnect';
export default ServerConnect;
@@ -1,54 +0,0 @@
/*
Copyright 2019 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { useAppContext } from 'teleterm/ui/appContextProvider';
export default function useServerConnect({ serverUri, onClose }: Props) {
const ctx = useAppContext();
const server = ctx.clustersService.getServer(serverUri);
const cluster = ctx.clustersService.findClusterByResource(serverUri);
const logins = cluster?.loggedInUser?.sshLoginsList || [];
const connect = (login: string) => {
const rootCluster =
ctx.clustersService.findRootClusterByResource(serverUri);
const documentsService = ctx.workspacesService.getWorkspaceDocumentService(
rootCluster.uri
);
const doc = documentsService.createTshNodeDocument(serverUri);
doc.title = `${login}@${server.hostname}`;
doc.login = login;
documentsService.add(doc);
documentsService.setLocation(doc.uri);
onClose();
};
return {
server,
logins,
connect,
onClose,
};
}
export type Props = {
onClose(): void;
serverUri: string;
};
export type State = ReturnType<typeof useServerConnect>;
@@ -19,14 +19,6 @@ import { routing } from 'teleterm/ui/uri';
import { tsh } from 'teleterm/ui/services/clusters/types';
const commands = {
// For handling the "Connect" button next to a server.
ssh: {
displayName: '',
description: '',
run(ctx: IAppContext, args: { serverUri: string }) {
ctx.modalsService.openProxySshDialog(args.serverUri);
},
},
// For handling "tsh ssh" executed from the command bar.
'tsh-ssh': {
displayName: '',
@@ -41,13 +41,6 @@ export class ModalsService extends ImmutableStore<Dialog> {
}));
}
openProxySshDialog(serverUri: string) {
this.setState(() => ({
kind: 'server-connect',
serverUri,
}));
}
openClusterConnectDialog(
clusterUri?: string,
onSuccess?: (clusterUri: string) => void
@@ -93,14 +86,8 @@ export interface DialogClusterRemove {
clusterTitle: string;
}
export interface DialogServerConnect {
kind: 'server-connect';
serverUri: string;
}
export type Dialog =
| DialogBase
| DialogClusterConnect
| DialogNewGateway
| DialogServerConnect
| DialogClusterRemove;