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 = {