From 1684e54c93a45bb3accf34fda542abbc0896da34 Mon Sep 17 00:00:00 2001
From: Yassine Bounekhla <56373201+rudream@users.noreply.github.com>
Date: Wed, 5 Mar 2025 12:49:34 -0500
Subject: [PATCH] update sidenav sections (#52770)
---
web/packages/design/src/Icon/Icons.story.tsx | 1 +
.../design/src/Icon/Icons/ArrowSquareIn.tsx | 66 +++++++++++++++++++
.../design/src/Icon/assets/ArrowSquareIn.svg | 4 ++
web/packages/design/src/Icon/index.ts | 1 +
.../teleport/src/Main/MainContainer.tsx | 5 +-
.../teleport/src/Navigation/CategoryIcon.tsx | 9 ++-
.../teleport/src/Navigation/Navigation.tsx | 12 ++--
.../teleport/src/Navigation/Section.tsx | 19 ++++--
.../teleport/src/Navigation/categories.ts | 14 ++--
web/packages/teleport/src/features.tsx | 41 ++++++++----
web/packages/teleport/src/types.ts | 3 +
11 files changed, 142 insertions(+), 33 deletions(-)
create mode 100644 web/packages/design/src/Icon/Icons/ArrowSquareIn.tsx
create mode 100644 web/packages/design/src/Icon/assets/ArrowSquareIn.svg
diff --git a/web/packages/design/src/Icon/Icons.story.tsx b/web/packages/design/src/Icon/Icons.story.tsx
index 51b33ceb4bd07..80ba1238487ec 100644
--- a/web/packages/design/src/Icon/Icons.story.tsx
+++ b/web/packages/design/src/Icon/Icons.story.tsx
@@ -51,6 +51,7 @@ export const Icons = () => (
+
diff --git a/web/packages/design/src/Icon/Icons/ArrowSquareIn.tsx b/web/packages/design/src/Icon/Icons/ArrowSquareIn.tsx
new file mode 100644
index 0000000000000..f327282447b91
--- /dev/null
+++ b/web/packages/design/src/Icon/Icons/ArrowSquareIn.tsx
@@ -0,0 +1,66 @@
+/**
+ * Teleport
+ * Copyright (C) 2023 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+/* MIT License
+
+Copyright (c) 2020 Phosphor Icons
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+import { forwardRef } from 'react';
+
+import { Icon, IconProps } from '../Icon';
+
+/*
+
+THIS FILE IS GENERATED. DO NOT EDIT.
+
+*/
+
+export const ArrowSquareIn = forwardRef(
+ ({ size = 24, color, ...otherProps }, ref) => (
+
+
+
+
+ )
+);
diff --git a/web/packages/design/src/Icon/assets/ArrowSquareIn.svg b/web/packages/design/src/Icon/assets/ArrowSquareIn.svg
new file mode 100644
index 0000000000000..5bcf540a25b0e
--- /dev/null
+++ b/web/packages/design/src/Icon/assets/ArrowSquareIn.svg
@@ -0,0 +1,4 @@
+
diff --git a/web/packages/design/src/Icon/index.ts b/web/packages/design/src/Icon/index.ts
index 79a733b145bb2..ce410ccb11ff6 100644
--- a/web/packages/design/src/Icon/index.ts
+++ b/web/packages/design/src/Icon/index.ts
@@ -40,6 +40,7 @@ export { ArrowForward } from './Icons/ArrowForward';
export { ArrowLeft } from './Icons/ArrowLeft';
export { ArrowLineLeft } from './Icons/ArrowLineLeft';
export { ArrowRight } from './Icons/ArrowRight';
+export { ArrowSquareIn } from './Icons/ArrowSquareIn';
export { ArrowSquareOut } from './Icons/ArrowSquareOut';
export { ArrowUp } from './Icons/ArrowUp';
export { ArrowsIn } from './Icons/ArrowsIn';
diff --git a/web/packages/teleport/src/Main/MainContainer.tsx b/web/packages/teleport/src/Main/MainContainer.tsx
index 75a4109c32d15..c1db852cb5eb9 100644
--- a/web/packages/teleport/src/Main/MainContainer.tsx
+++ b/web/packages/teleport/src/Main/MainContainer.tsx
@@ -26,9 +26,8 @@ import styled from 'styled-components';
export const MainContainer = styled.div`
display: flex;
flex: 1;
- --sidebar-width: 256px;
- --sidenav-width: 76px;
- --sidenav-panel-width: 224px;
+ --sidenav-width: 84px;
+ --sidenav-panel-width: 264px;
overflow: hidden;
margin-top: ${p => p.theme.topBarHeight[0]}px;
@media screen and (min-width: ${p => p.theme.breakpoints.small}px) {
diff --git a/web/packages/teleport/src/Navigation/CategoryIcon.tsx b/web/packages/teleport/src/Navigation/CategoryIcon.tsx
index 1d1b4a24e673a..c711d68cff4ec 100644
--- a/web/packages/teleport/src/Navigation/CategoryIcon.tsx
+++ b/web/packages/teleport/src/Navigation/CategoryIcon.tsx
@@ -40,13 +40,13 @@ export function CategoryIcon({
case NavigationCategory.Resources:
Icon = Icons.Server;
break;
- case NavigationCategory.Access:
+ case NavigationCategory.ZeroTrustAccess:
Icon = Icons.KeyHole;
break;
- case NavigationCategory.Identity:
+ case NavigationCategory.IdentityGovernance:
Icon = Icons.FingerprintSimple;
break;
- case NavigationCategory.Policy:
+ case NavigationCategory.IdentitySecurity:
Icon = Icons.ShieldCheck;
break;
case NavigationCategory.Audit:
@@ -58,6 +58,9 @@ export function CategoryIcon({
case CustomNavigationCategory.Search:
Icon = Icons.Magnifier;
break;
+ case NavigationCategory.MachineWorkloadId:
+ Icon = Icons.Bots;
+ break;
default:
return null;
}
diff --git a/web/packages/teleport/src/Navigation/Navigation.tsx b/web/packages/teleport/src/Navigation/Navigation.tsx
index 2864ea7b14159..7d733b32c5468 100644
--- a/web/packages/teleport/src/Navigation/Navigation.tsx
+++ b/web/packages/teleport/src/Navigation/Navigation.tsx
@@ -110,6 +110,8 @@ export type NavigationSubsection = {
* Note that this is merely extra logic, and does not replace the default routing behaviour of a subsection which will navigate the user to the route.
*/
onClick?: () => void;
+ /** isHyperLink is whether this subsection is merely a hyperlink/shortcut to another subsection. */
+ isHyperLink?: boolean;
};
function getNavigationSections(
@@ -158,12 +160,11 @@ function getSubsectionsForCategory(
exact: feature.navigationItem.exact,
icon: feature.navigationItem.icon,
searchableTags: feature.navigationItem.searchableTags,
+ isHyperLink: feature.isHyperLink,
};
});
}
-// getNavSubsectionForRoute returns the sidenav subsection that the user is correctly on (based on route).
-// Note that it is possible for this not to return anything, such as in the case where the user is on a page that isn't in the sidenav (eg. Account Settings).
/**
* getTopMenuSection returns a NavigationSection with the top menu items. This is not used in the sidenav, but will be used to make the top menu items searchable.
*/
@@ -184,10 +185,12 @@ function getTopMenuSection(features: TeleportFeature[]): NavigationSection {
};
}
+/** getNavSubsectionForRoute returns the sidenav subsection that the user is correctly on (based on route).
+ * Note that it is possible for this not to return anything, such as in the case where the user is on a page that isn't in the sidenav (eg. Account Settings). **/
function getNavSubsectionForRoute(
features: TeleportFeature[],
route: history.Location | Location
-): NavigationSubsection {
+): NavigationSubsection | undefined {
let feature = features
.filter(feature => Boolean(feature.route))
.find(feature =>
@@ -206,7 +209,8 @@ function getNavSubsectionForRoute(
if (
!feature ||
- (!feature.category && !feature.topMenuItem && !feature.navigationItem)
+ (!feature.category && !feature.topMenuItem && !feature.navigationItem) ||
+ feature.isHyperLink
) {
return;
}
diff --git a/web/packages/teleport/src/Navigation/Section.tsx b/web/packages/teleport/src/Navigation/Section.tsx
index 5681b7836bcde..0c4b5fd15aa41 100644
--- a/web/packages/teleport/src/Navigation/Section.tsx
+++ b/web/packages/teleport/src/Navigation/Section.tsx
@@ -21,7 +21,7 @@ import { NavLink } from 'react-router-dom';
import styled, { css, useTheme } from 'styled-components';
import { Box, ButtonIcon, Flex, P2, Text } from 'design';
-import { ArrowLineLeft } from 'design/Icon';
+import { ArrowLineLeft, ArrowSquareIn } from 'design/Icon';
import { Theme } from 'design/theme';
import { HoverTooltip, IconTooltip } from 'design/Tooltip';
@@ -100,7 +100,10 @@ export function DefaultSection({
{!section.standalone &&
section.subsections.map(subsection => (
{subsection.title}
+ {subsection.isHyperLink && (
+
+ )}
))}
@@ -170,7 +176,7 @@ export function StandaloneSection({
);
}
-export const rightPanelWidth = 236;
+export const rightPanelWidth = 274;
export const RightPanel = styled(Box).attrs({ px: '5px' })<{
isVisible: boolean;
@@ -266,8 +272,8 @@ export const CategoryButton = styled.button<{
$active: boolean;
isExpanded?: boolean;
}>`
- min-height: 60px;
- min-width: 60px;
+ height: 68px;
+ width: 68px;
cursor: pointer;
outline: hidden;
border: none;
@@ -282,11 +288,12 @@ export const CategoryButton = styled.button<{
justify-content: center;
gap: ${props => props.theme.space[1]}px;
font-family: ${props => props.theme.font};
+ padding: ${props => props.theme.space[2]}px ${props => props.theme.space[1]}px;
font-size: ${props => props.theme.typography.body4.fontSize};
font-weight: ${props => props.theme.typography.body4.fontWeight};
letter-spacing: ${props => props.theme.typography.body4.letterSpacing};
- line-height: ${props => props.theme.typography.body4.lineHeight};
+ line-height: 12px;
text-decoration: none;
${props => getCategoryStyles(props.theme, props.$active, props.isExpanded)}
diff --git a/web/packages/teleport/src/Navigation/categories.ts b/web/packages/teleport/src/Navigation/categories.ts
index 6bc60bd20f818..f51ca4b39d024 100644
--- a/web/packages/teleport/src/Navigation/categories.ts
+++ b/web/packages/teleport/src/Navigation/categories.ts
@@ -18,9 +18,10 @@
export enum NavigationCategory {
Resources = 'Resources',
- Access = 'Access',
- Identity = 'Identity',
- Policy = 'Policy',
+ ZeroTrustAccess = 'Zero Trust Access',
+ MachineWorkloadId = 'Machine & Workload ID',
+ IdentityGovernance = 'Identity Governance',
+ IdentitySecurity = 'Identity Security',
Audit = 'Audit',
AddNew = 'Add New',
}
@@ -43,9 +44,10 @@ export enum CustomNavigationSubcategory {
export type SidenavCategory = NavigationCategory | CustomNavigationCategory;
export const NAVIGATION_CATEGORIES = [
- NavigationCategory.Access,
- NavigationCategory.Identity,
- NavigationCategory.Policy,
+ NavigationCategory.ZeroTrustAccess,
+ NavigationCategory.MachineWorkloadId,
+ NavigationCategory.IdentityGovernance,
+ NavigationCategory.IdentitySecurity,
NavigationCategory.Audit,
NavigationCategory.AddNew,
];
diff --git a/web/packages/teleport/src/features.tsx b/web/packages/teleport/src/features.tsx
index 43d8132cfb8cb..982b3ad692d40 100644
--- a/web/packages/teleport/src/features.tsx
+++ b/web/packages/teleport/src/features.tsx
@@ -17,6 +17,7 @@
*/
import {
+ AddCircle,
Bots as BotsIcon,
CirclePlay,
ClipboardUser,
@@ -102,7 +103,7 @@ class AccessRequests implements TeleportFeature {
}
export class FeatureJoinTokens implements TeleportFeature {
- category = NavigationCategory.Access;
+ category = NavigationCategory.ZeroTrustAccess;
navigationItem = {
title: NavTitle.JoinTokens,
@@ -202,7 +203,7 @@ export class FeatureSessions implements TeleportFeature {
// - Access
export class FeatureUsers implements TeleportFeature {
- category = NavigationCategory.Access;
+ category = NavigationCategory.ZeroTrustAccess;
route = {
title: 'Manage Users',
@@ -233,7 +234,7 @@ export class FeatureUsers implements TeleportFeature {
}
export class FeatureBots implements TeleportFeature {
- category = NavigationCategory.Access;
+ category = NavigationCategory.MachineWorkloadId;
route = {
title: 'Manage Bots',
@@ -266,6 +267,24 @@ export class FeatureBots implements TeleportFeature {
}
}
+export class FeatureAddBotsShortcut implements TeleportFeature {
+ category = NavigationCategory.MachineWorkloadId;
+ isHyperLink = true;
+
+ hasAccess(flags: FeatureFlags) {
+ return flags.addBots;
+ }
+
+ navigationItem = {
+ title: NavTitle.NewBotShortcut,
+ icon: AddCircle,
+ exact: true,
+ getLink() {
+ return cfg.getBotsNewRoute();
+ },
+ };
+}
+
export class FeatureAddBots implements TeleportFeature {
category = NavigationCategory.AddNew;
@@ -296,7 +315,7 @@ export class FeatureAddBots implements TeleportFeature {
}
export class FeatureRoles implements TeleportFeature {
- category = NavigationCategory.Access;
+ category = NavigationCategory.ZeroTrustAccess;
route = {
title: 'Manage User Roles',
@@ -328,7 +347,7 @@ export class FeatureRoles implements TeleportFeature {
}
export class FeatureAuthConnectors implements TeleportFeature {
- category = NavigationCategory.Access;
+ category = NavigationCategory.ZeroTrustAccess;
route = {
title: 'Manage Auth Connectors',
@@ -357,7 +376,7 @@ export class FeatureAuthConnectors implements TeleportFeature {
}
export class FeatureLocks implements TeleportFeature {
- category = NavigationCategory.Identity;
+ category = NavigationCategory.IdentityGovernance;
route = {
title: 'Session & Identity Locks',
@@ -431,7 +450,7 @@ export class FeatureDiscover implements TeleportFeature {
}
export class FeatureIntegrations implements TeleportFeature {
- category = NavigationCategory.Access;
+ category = NavigationCategory.ZeroTrustAccess;
hasAccess(flags: FeatureFlags) {
// if feature hiding is enabled, only show
@@ -550,7 +569,7 @@ export class FeatureAudit implements TeleportFeature {
// - Clusters
export class FeatureClusters implements TeleportFeature {
- category = NavigationCategory.Access;
+ category = NavigationCategory.ZeroTrustAccess;
route = {
title: 'Clusters',
@@ -583,7 +602,7 @@ export class FeatureClusters implements TeleportFeature {
}
export class FeatureTrust implements TeleportFeature {
- category = NavigationCategory.Access;
+ category = NavigationCategory.ZeroTrustAccess;
route = {
title: 'Trusted Root Clusters',
@@ -606,7 +625,7 @@ export class FeatureTrust implements TeleportFeature {
}
class FeatureDeviceTrust implements TeleportFeature {
- category = NavigationCategory.Identity;
+ category = NavigationCategory.IdentityGovernance;
route = {
title: 'Trusted Devices',
path: cfg.routes.deviceTrust,
@@ -703,7 +722,6 @@ export class FeatureHelpAndSupport implements TeleportFeature {
export function getOSSFeatures(): TeleportFeature[] {
return [
// Resources
- // TODO(rudream): Implement shortcuts to pinned/nodes/apps/dbs/desktops/kubes.
new FeatureUnifiedResources(),
// AddNew
@@ -714,6 +732,7 @@ export function getOSSFeatures(): TeleportFeature[] {
// - Access
new FeatureUsers(),
new FeatureBots(),
+ new FeatureAddBotsShortcut(),
new FeatureJoinTokens(),
new FeatureRoles(),
new FeatureAuthConnectors(),
diff --git a/web/packages/teleport/src/types.ts b/web/packages/teleport/src/types.ts
index d78d99f5082d8..4607ef859e518 100644
--- a/web/packages/teleport/src/types.ts
+++ b/web/packages/teleport/src/types.ts
@@ -67,6 +67,7 @@ export enum NavTitle {
EnrollNewIntegration = 'Integration',
NewAccessList = 'Access List',
NewBot = 'Bot',
+ NewBotShortcut = 'Enroll New Bot',
// Identity Governance & Security
AccessLists = 'Access Lists',
@@ -145,6 +146,8 @@ export interface TeleportFeature {
highlightKey?: string;
/** showInDashboard is whether this page should be shown in the navigation for dashboard tenants. Any feature without this flag will not be shown for dashboards. */
showInDashboard?: boolean;
+ /** isHyperLink is whether this subsection is merely a hyperlink/shortcut to another subsection. */
+ isHyperLink?: boolean;
}
export type StickyCluster = {