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%;
`;