mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
Use connection dropdown instead of modal for supplying SSH username
This commit is contained in:
committed by
Grzegorz Zdunek
parent
4f8704c083
commit
2d07c288a4
@@ -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;
|
||||
|
||||
|
||||
+21
@@ -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>
|
||||
);
|
||||
);
|
||||
+27
-4
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user