diff --git a/web/packages/teleterm/src/mainProcess/windowsManager.ts b/web/packages/teleterm/src/mainProcess/windowsManager.ts index 5ec7a2e1c5038..44e705f3f7969 100644 --- a/web/packages/teleterm/src/mainProcess/windowsManager.ts +++ b/web/packages/teleterm/src/mainProcess/windowsManager.ts @@ -64,7 +64,7 @@ export class WindowsManager { width: windowState.width, height: windowState.height, backgroundColor: activeTheme.colors.levels.sunken, - minWidth: 400, + minWidth: 490, minHeight: 300, show: false, autoHideMenuBar: true, diff --git a/web/packages/teleterm/src/ui/ConnectMyComputer/NavigationMenu.story.tsx b/web/packages/teleterm/src/ui/ConnectMyComputer/NavigationMenu.story.tsx index 9842bbae137a4..f4e863e8fe737 100644 --- a/web/packages/teleterm/src/ui/ConnectMyComputer/NavigationMenu.story.tsx +++ b/web/packages/teleterm/src/ui/ConnectMyComputer/NavigationMenu.story.tsx @@ -16,13 +16,10 @@ import React from 'react'; -import { Flex } from 'design'; - import { wait } from 'shared/utils/wait'; import { MockWorkspaceContextProvider } from 'teleterm/ui/fixtures/MockWorkspaceContextProvider'; import { makeRootCluster } from 'teleterm/services/tshd/testHelpers'; -import * as types from 'teleterm/ui/services/workspacesService'; import { MockAppContext } from 'teleterm/ui/fixtures/mocks'; import { MockAppContextProvider } from 'teleterm/ui/fixtures/MockAppContextProvider'; @@ -123,20 +120,6 @@ function ShowState({ const cluster = makeRootCluster({ features: { isUsageBasedBilling: true, advancedAccessWorkflows: false }, }); - cluster.loggedInUser.acl.tokens = { - create: true, - use: true, - read: true, - list: true, - edit: true, - pb_delete: true, - }; - const doc: types.DocumentConnectMyComputer = { - kind: 'doc.connect_my_computer', - rootClusterUri: cluster.uri, - title: 'Connect My Computer', - uri: '/docs/123', - }; const appContext = new MockAppContext(); appContext.clustersService.state.clusters.set(cluster.uri, cluster); appContext.configService = createMockConfigService({ @@ -146,8 +129,8 @@ function ShowState({ draftState.rootClusterUri = cluster.uri; draftState.workspaces[cluster.uri] = { localClusterUri: cluster.uri, - documents: [doc], - location: doc.uri, + documents: [], + location: undefined, accessRequests: undefined, }; }); @@ -160,9 +143,7 @@ function ShowState({ - - - + diff --git a/web/packages/teleterm/src/ui/ConnectMyComputer/NavigationMenu.tsx b/web/packages/teleterm/src/ui/ConnectMyComputer/NavigationMenu.tsx index ccfa3c0212687..ac8405ceda908 100644 --- a/web/packages/teleterm/src/ui/ConnectMyComputer/NavigationMenu.tsx +++ b/web/packages/teleterm/src/ui/ConnectMyComputer/NavigationMenu.tsx @@ -21,8 +21,6 @@ import { Laptop, Warning } from 'design/Icon'; import { Attempt, AttemptStatus } from 'shared/hooks/useAsync'; -import { useAppContext } from 'teleterm/ui/appContextProvider'; -import { ClusterUri } from 'teleterm/ui/uri'; import { useWorkspaceContext } from 'teleterm/ui/Documents'; import { assertUnreachable } from 'teleterm/ui/utils'; @@ -31,32 +29,24 @@ import { useConnectMyComputerContext, } from './connectMyComputerContext'; -interface NavigationMenuProps { - clusterUri: ClusterUri; -} - /** * IndicatorStatus combines a couple of different states into a single enum which dictates the * decorative look of NavigationMenu. */ type IndicatorStatus = AttemptStatus; -export function NavigationMenu(props: NavigationMenuProps) { +export function NavigationMenu() { const iconRef = useRef(); const [isMenuOpened, setIsMenuOpened] = useState(false); - const appCtx = useAppContext(); const { documentsService, rootClusterUri } = useWorkspaceContext(); const { isAgentConfiguredAttempt, currentAction, canUse } = useConnectMyComputerContext(); - // DocumentCluster renders this component only if the cluster exists. - const cluster = appCtx.clustersService.findCluster(props.clusterUri); const indicatorStatus = getIndicatorStatus( currentAction, isAgentConfiguredAttempt ); - // Don't show the navigation icon for leaf clusters. - if (cluster.leaf || !canUse) { + if (!canUse) { return null; } @@ -94,12 +84,11 @@ export function NavigationMenu(props: NavigationMenuProps) { getContentAnchorEl={null} open={isMenuOpened} anchorEl={iconRef.current} - anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }} - transformOrigin={{ vertical: 'top', horizontal: 'right' }} + anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} + transformOrigin={{ vertical: 'top', horizontal: 'left' }} onClose={() => setIsMenuOpened(false)} menuListCss={() => css` - width: 150px; display: flex; flex-direction: column; ` @@ -179,41 +168,45 @@ export const MenuIcon = forwardRef( const indicatorStatusToStyledStatus = ( indicatorStatus: IndicatorStatus ): JSX.Element => { - switch (indicatorStatus) { - case '': { - return null; - } - case 'processing': { - return ( - - ); - } - case 'error': { - return ; - } - case 'success': { - return ; - } - } + return ( + { + const hasFinished = + props.status === 'success' || props.status === 'error'; + return hasFinished ? '0' : 'infinite'; + }}; + visibility: ${props => (props.status === '' ? 'hidden' : 'visible')}; + background: ${props => getIndicatorColor(props.status, props.theme)}; + `} + /> + ); }; +function getIndicatorColor(status: IndicatorStatus, theme: any): string { + switch (status) { + case 'processing': + case 'success': + return theme.colors.success; + case 'error': + return theme.colors.error.main; + } +} + const StyledButton = styled(Button)` position: relative; background: ${props => props.theme.colors.spotBackground[0]}; diff --git a/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/ClusterResources.tsx b/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/ClusterResources.tsx index 91a2269746c00..3f75954e0d383 100644 --- a/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/ClusterResources.tsx +++ b/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/ClusterResources.tsx @@ -19,7 +19,6 @@ import styled from 'styled-components'; import { Flex } from 'design'; import { useClusterContext } from 'teleterm/ui/DocumentCluster/clusterContext'; -import { ConnectMyComputerNavigationMenu } from 'teleterm/ui/ConnectMyComputer'; import SideNav from './SideNav'; import Servers from './Servers'; @@ -42,10 +41,7 @@ export default function ClusterResources() { return ( - - - - + {clusterCtx.isLocationActive('/resources/servers') && } {clusterCtx.isLocationActive('/resources/databases') && } diff --git a/web/packages/teleterm/src/ui/Documents/DocumentsRenderer.tsx b/web/packages/teleterm/src/ui/Documents/DocumentsRenderer.tsx index 28d75cd9516d9..0dbae97893314 100644 --- a/web/packages/teleterm/src/ui/Documents/DocumentsRenderer.tsx +++ b/web/packages/teleterm/src/ui/Documents/DocumentsRenderer.tsx @@ -15,6 +15,7 @@ */ import React, { useMemo } from 'react'; +import { createPortal } from 'react-dom'; import styled from 'styled-components'; /* eslint-disable @typescript-eslint/ban-ts-comment*/ @@ -35,6 +36,7 @@ import { DocumentTerminal } from 'teleterm/ui/DocumentTerminal'; import { ConnectMyComputerContextProvider, DocumentConnectMyComputer, + ConnectMyComputerNavigationMenu, } from 'teleterm/ui/ConnectMyComputer'; import { DocumentGatewayKube } from 'teleterm/ui/DocumentGatewayKube'; @@ -44,7 +46,9 @@ import { RootClusterUri } from 'teleterm/ui/uri'; import { WorkspaceContextProvider } from './workspaceContext'; import { KeyboardShortcutsPanel } from './KeyboardShortcutsPanel'; -export function DocumentsRenderer() { +export function DocumentsRenderer(props: { + topBarContainerRef: React.MutableRefObject; +}) { const { workspacesService } = useAppContext(); function renderDocuments(documentsService: DocumentsService) { @@ -87,6 +91,12 @@ export function DocumentsRenderer() { ) : ( )} + {workspace.rootClusterUri === + workspacesService.getRootClusterUri() && + createPortal( + , + props.topBarContainerRef?.current + )} diff --git a/web/packages/teleterm/src/ui/LayoutManager.tsx b/web/packages/teleterm/src/ui/LayoutManager.tsx index c690ecdbbcbca..b34b4e195d0c1 100644 --- a/web/packages/teleterm/src/ui/LayoutManager.tsx +++ b/web/packages/teleterm/src/ui/LayoutManager.tsx @@ -13,7 +13,7 @@ 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 React, { useRef } from 'react'; import { Flex } from 'design'; /* eslint-disable @typescript-eslint/ban-ts-comment*/ // @ts-ignore @@ -25,9 +25,11 @@ import { StatusBar } from 'teleterm/ui/StatusBar'; import { NotificationsHost } from 'teleterm/ui/components/Notifcations'; export function LayoutManager() { + const topBarContainerRef = useRef(); + return ( - + - + diff --git a/web/packages/teleterm/src/ui/TabHost/TabHost.test.tsx b/web/packages/teleterm/src/ui/TabHost/TabHost.test.tsx index 4cbf3e7267b21..61a21a92d7a5a 100644 --- a/web/packages/teleterm/src/ui/TabHost/TabHost.test.tsx +++ b/web/packages/teleterm/src/ui/TabHost/TabHost.test.tsx @@ -154,7 +154,7 @@ function getTestSetup({ documents }: { documents: Document[] }) { const utils = render( - + ); diff --git a/web/packages/teleterm/src/ui/TabHost/TabHost.tsx b/web/packages/teleterm/src/ui/TabHost/TabHost.tsx index 7a76a18c9e35d..33cf46e0c0ff3 100644 --- a/web/packages/teleterm/src/ui/TabHost/TabHost.tsx +++ b/web/packages/teleterm/src/ui/TabHost/TabHost.tsx @@ -29,18 +29,26 @@ import { useTabShortcuts } from './useTabShortcuts'; import { useNewTabOpener } from './useNewTabOpener'; import { ClusterConnectPanel } from './ClusterConnectPanel/ClusterConnectPanel'; -export function TabHostContainer() { +export function TabHostContainer(props: { + topBarContainerRef: React.MutableRefObject; +}) { const ctx = useAppContext(); ctx.workspacesService.useState(); const isRootClusterSelected = !!ctx.workspacesService.getRootClusterUri(); if (isRootClusterSelected) { - return ; + return ; } return ; } -export function TabHost({ ctx }: { ctx: IAppContext }) { +export function TabHost({ + ctx, + topBarContainerRef, +}: { + ctx: IAppContext; + topBarContainerRef: React.MutableRefObject; +}) { const documentsService = ctx.workspacesService.getActiveWorkspaceDocumentService(); const activeDocument = documentsService?.getActive(); @@ -111,7 +119,7 @@ export function TabHost({ ctx }: { ctx: IAppContext }) { closeTabTooltip={getLabelWithAccelerator('Close', 'closeTab')} /> - + ); } diff --git a/web/packages/teleterm/src/ui/TopBar/TopBar.tsx b/web/packages/teleterm/src/ui/TopBar/TopBar.tsx index 1d5d49a7ec17a..f208c825b8aee 100644 --- a/web/packages/teleterm/src/ui/TopBar/TopBar.tsx +++ b/web/packages/teleterm/src/ui/TopBar/TopBar.tsx @@ -25,11 +25,14 @@ import { Clusters } from './Clusters'; import { Identity } from './Identity'; import { AdditionalActions } from './AdditionalActions'; -export function TopBar() { +export function TopBar(props: { + topBarContainerRef: React.MutableRefObject; +}) { return ( +
@@ -61,6 +64,7 @@ const CentralContainer = styled(Flex).attrs({ gap: 3 })` const JustifyLeft = styled(Flex).attrs({ gap: 3 })` align-items: center; + min-width: 80px; // reserves space for CMC icon to prevent layout shifting height: 100%; `;