diff --git a/web/packages/design/src/Alert/Alert.tsx b/web/packages/design/src/Alert/Alert.tsx index 5e471280ffe24..c674aff2b1b79 100644 --- a/web/packages/design/src/Alert/Alert.tsx +++ b/web/packages/design/src/Alert/Alert.tsx @@ -20,6 +20,8 @@ import React, { useState } from 'react'; import styled, { useTheme } from 'styled-components'; import { style, color, ColorProps } from 'styled-system'; +import { IconProps } from 'design/Icon/Icon'; + import { space, SpaceProps, width, WidthProps } from '../system'; import { Theme } from '../theme'; import * as Icon from '../Icon'; @@ -111,7 +113,7 @@ interface Props { /** Additional description to be displayed below the main content. */ details?: React.ReactNode; /** Overrides the icon specified by {@link AlertProps.kind}. */ - icon?: React.ComponentType; + icon?: React.ComponentType; /** If specified, causes the alert to display a primary action button. */ primaryAction?: Action; /** If specified, causes the alert to display a secondary action button. */ @@ -253,8 +255,8 @@ const AlertIcon = ({ ...otherProps }: { kind: AlertKind | BannerKind; - customIcon?: React.ComponentType; -} & Icon.IconProps) => { + customIcon?: React.ComponentType; +} & IconProps) => { const commonProps = { role: 'graphics-symbol', ...otherProps }; if (CustomIcon) { return ; diff --git a/web/packages/design/src/Icon/Icons.story.tsx b/web/packages/design/src/Icon/Icons.story.tsx index a511f5692b4ca..4a792aeec912c 100644 --- a/web/packages/design/src/Icon/Icons.story.tsx +++ b/web/packages/design/src/Icon/Icons.story.tsx @@ -135,6 +135,7 @@ export const Icons = () => ( + @@ -152,6 +153,7 @@ export const Icons = () => ( + @@ -170,6 +172,7 @@ export const Icons = () => ( + diff --git a/web/packages/design/src/Icon/Icons/KeyHole.tsx b/web/packages/design/src/Icon/Icons/KeyHole.tsx new file mode 100644 index 0000000000000..afc86a7543647 --- /dev/null +++ b/web/packages/design/src/Icon/Icons/KeyHole.tsx @@ -0,0 +1,73 @@ +/** + * 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 React from 'react'; + +import { Icon, IconProps } from '../Icon'; + +/* + +THIS FILE IS GENERATED. DO NOT EDIT. + +*/ + +export function KeyHole({ size = 24, color, ...otherProps }: IconProps) { + return ( + + + + + ); +} diff --git a/web/packages/design/src/Icon/Icons/LockKey.tsx b/web/packages/design/src/Icon/Icons/LockKey.tsx new file mode 100644 index 0000000000000..65a953566d71d --- /dev/null +++ b/web/packages/design/src/Icon/Icons/LockKey.tsx @@ -0,0 +1,73 @@ +/** + * 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 React from 'react'; + +import { Icon, IconProps } from '../Icon'; + +/* + +THIS FILE IS GENERATED. DO NOT EDIT. + +*/ + +export function LockKey({ size = 24, color, ...otherProps }: IconProps) { + return ( + + + + + ); +} diff --git a/web/packages/design/src/Icon/Icons/PlugsConnected.tsx b/web/packages/design/src/Icon/Icons/PlugsConnected.tsx new file mode 100644 index 0000000000000..ec991cd95b2ee --- /dev/null +++ b/web/packages/design/src/Icon/Icons/PlugsConnected.tsx @@ -0,0 +1,72 @@ +/** + * 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 React from 'react'; + +import { Icon, IconProps } from '../Icon'; + +/* + +THIS FILE IS GENERATED. DO NOT EDIT. + +*/ + +export function PlugsConnected({ size = 24, color, ...otherProps }: IconProps) { + return ( + + + + + + + + ); +} diff --git a/web/packages/design/src/Icon/assets/KeyHole.svg b/web/packages/design/src/Icon/assets/KeyHole.svg new file mode 100644 index 0000000000000..c4c69dfaf4c8e --- /dev/null +++ b/web/packages/design/src/Icon/assets/KeyHole.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/packages/design/src/Icon/assets/LockKey.svg b/web/packages/design/src/Icon/assets/LockKey.svg new file mode 100644 index 0000000000000..554f55b8d71d4 --- /dev/null +++ b/web/packages/design/src/Icon/assets/LockKey.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/packages/design/src/Icon/assets/PlugsConnected.svg b/web/packages/design/src/Icon/assets/PlugsConnected.svg new file mode 100644 index 0000000000000..2ce54755155bf --- /dev/null +++ b/web/packages/design/src/Icon/assets/PlugsConnected.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/web/packages/design/src/Icon/index.ts b/web/packages/design/src/Icon/index.ts index 57f2fa84d3a11..6e24c134497db 100644 --- a/web/packages/design/src/Icon/index.ts +++ b/web/packages/design/src/Icon/index.ts @@ -22,7 +22,7 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export { Icon, type IconProps } from './Icon'; +export { Icon } from './Icon'; export { Add } from './Icons/Add'; export { AddCircle } from './Icons/AddCircle'; @@ -121,6 +121,7 @@ export { Info } from './Icons/Info'; export { Integrations } from './Icons/Integrations'; export { Invoices } from './Icons/Invoices'; export { Key } from './Icons/Key'; +export { KeyHole } from './Icons/KeyHole'; export { Keyboard } from './Icons/Keyboard'; export { Keypair } from './Icons/Keypair'; export { Kubernetes } from './Icons/Kubernetes'; @@ -138,6 +139,7 @@ export { ListMagnifyingGlass } from './Icons/ListMagnifyingGlass'; export { ListThin } from './Icons/ListThin'; export { ListView } from './Icons/ListView'; export { Lock } from './Icons/Lock'; +export { LockKey } from './Icons/LockKey'; export { Logout } from './Icons/Logout'; export { Magnifier } from './Icons/Magnifier'; export { MagnifyingMinus } from './Icons/MagnifyingMinus'; @@ -156,6 +158,7 @@ export { PaperPlane } from './Icons/PaperPlane'; export { Password } from './Icons/Password'; export { Pencil } from './Icons/Pencil'; export { Planet } from './Icons/Planet'; +export { PlugsConnected } from './Icons/PlugsConnected'; export { Plus } from './Icons/Plus'; export { PowerSwitch } from './Icons/PowerSwitch'; export { Printer } from './Icons/Printer'; diff --git a/web/packages/teleport/src/Main/MainContainer.tsx b/web/packages/teleport/src/Main/MainContainer.tsx index 4f7910b80e5dd..75a4109c32d15 100644 --- a/web/packages/teleport/src/Main/MainContainer.tsx +++ b/web/packages/teleport/src/Main/MainContainer.tsx @@ -29,11 +29,9 @@ export const MainContainer = styled.div` --sidebar-width: 256px; --sidenav-width: 76px; --sidenav-panel-width: 224px; + overflow: hidden; margin-top: ${p => p.theme.topBarHeight[0]}px; @media screen and (min-width: ${p => p.theme.breakpoints.small}px) { margin-top: ${p => p.theme.topBarHeight[1]}px; } - @media screen and (min-width: ${p => p.theme.breakpoints.large}px) { - margin-top: ${p => p.theme.topBarHeight[2]}px; - } `; diff --git a/web/packages/teleport/src/Navigation/SideNavigation/CategoryIcon.tsx b/web/packages/teleport/src/Navigation/SideNavigation/CategoryIcon.tsx index feb2135801050..944b53c218b2e 100644 --- a/web/packages/teleport/src/Navigation/SideNavigation/CategoryIcon.tsx +++ b/web/packages/teleport/src/Navigation/SideNavigation/CategoryIcon.tsx @@ -41,7 +41,7 @@ export function CategoryIcon({ Icon = Icons.Server; break; case NavigationCategory.Access: - Icon = Icons.Lock; + Icon = Icons.KeyHole; break; case NavigationCategory.Identity: Icon = Icons.FingerprintSimple; diff --git a/web/packages/teleport/src/Navigation/SideNavigation/Navigation.tsx b/web/packages/teleport/src/Navigation/SideNavigation/Navigation.tsx index 9d39688b52156..2b532b44e86ac 100644 --- a/web/packages/teleport/src/Navigation/SideNavigation/Navigation.tsx +++ b/web/packages/teleport/src/Navigation/SideNavigation/Navigation.tsx @@ -16,10 +16,10 @@ * along with this program. If not, see . */ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useEffect, useRef } from 'react'; import styled, { useTheme } from 'styled-components'; import { matchPath, useHistory } from 'react-router'; -import { Text, Flex, Box } from 'design'; +import { Text, Flex, Box, P2 } from 'design'; import { ToolTipInfo } from 'shared/components/ToolTip'; @@ -123,6 +123,8 @@ function getSubsectionsForCategory( }); } +// 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 @@ -150,14 +152,64 @@ function getNavSubsectionForRoute( }; } +/** + * useDebounceClose adds a debounce to closing drawers, this is to prevent the drawer closing if the user overshoots it, giving them a slight delay to re-enter the drawer. + */ +function useDebounceClose( + value: T | null, + delay: number, + isClosing: boolean +): T | null { + const [debouncedValue, setDebouncedValue] = useState(value); + const timeoutRef = useRef(); + + useEffect(() => { + // Clear any existing timeout + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + // If we're closing the drarwer as opposed to switching to a different section (value is null and isClosing is true), apply debounce. + if (value === null && isClosing) { + timeoutRef.current = setTimeout(() => { + setDebouncedValue(null); + }, delay); + } else { + // For opening or any other change, update immediately. + setDebouncedValue(value); + } + + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, [value, delay, isClosing]); + + return debouncedValue; +} + export function Navigation() { const features = useFeatures(); const history = useHistory(); - const [expandedSection, setExpandedSection] = - useState(null); - const currentView = getNavSubsectionForRoute(features, history.location); + const [targetSection, setTargetSection] = useState( + null + ); + const [isClosing, setIsClosing] = useState(false); + const debouncedSection = useDebounceClose(targetSection, 200, isClosing); const [previousExpandedSection, setPreviousExpandedSection] = useState(); + const navigationTimeoutRef = useRef(); + + // Clear navigation timeout on unmount. + useEffect(() => { + return () => { + if (navigationTimeoutRef.current) { + clearTimeout(navigationTimeoutRef.current); + } + }; + }, []); + const currentView = getNavSubsectionForRoute(features, history.location); const navSections = getNavigationSections(features).filter( section => section.subsections.length @@ -165,24 +217,59 @@ export function Navigation() { const handleSetExpandedSection = useCallback( (section: NavigationSection) => { + setIsClosing(false); if (!section.standalone) { - setPreviousExpandedSection(expandedSection); - setExpandedSection(section); + setPreviousExpandedSection(debouncedSection); + setTargetSection(section); } else { setPreviousExpandedSection(null); - setExpandedSection(null); + setTargetSection(null); } }, - [expandedSection] + [debouncedSection] ); - const resetExpandedSection = useCallback(() => { + const resetExpandedSection = useCallback((closeAfterDelay = true) => { + setIsClosing(closeAfterDelay); setPreviousExpandedSection(null); - setExpandedSection(null); + setTargetSection(null); }, []); + // Handler for navigation actions + const handleNavigation = useCallback( + (route: string) => { + history.push(route); + + // Clear any existing timeout + if (navigationTimeoutRef.current) { + clearTimeout(navigationTimeoutRef.current); + } + + // Add a small delay to the close to allow the user to see some feedback (see the section they clicked become active). + navigationTimeoutRef.current = setTimeout(() => { + resetExpandedSection(false); + }, 150); + }, + [resetExpandedSection, history] + ); + + // Hide the nav if the current feature has hideNavigation set to true. + const hideNav = features.find( + f => + f.route && + matchPath(history.location.pathname, { + path: f.route.path, + exact: f.route.exact ?? false, + }) + )?.hideNavigation; + + if (hideNav) { + return null; + } + return ( resetExpandedSection()} onKeyUp={e => e.key === 'Escape' && resetExpandedSection()} onBlur={(event: React.FocusEvent) => { @@ -200,73 +287,81 @@ export function Navigation() { {navSections.map(section => ( -
handleSetExpandedSection(section)} - aria-controls={`panel-${expandedSection?.category}`} - onClick={() => { - if (section.standalone) { - history.push(section.subsections[0].route); + + {section.category === 'Add New' && } +
handleSetExpandedSection(section)} + aria-controls={`panel-${debouncedSection?.category}`} + onClick={() => { + if (section.standalone) { + handleNavigation(section.subsections[0].route); + } + }} + isExpanded={ + !!debouncedSection && + !debouncedSection.standalone && + section.category === debouncedSection?.category } - }} - isExpanded={ - !!expandedSection && - !expandedSection.standalone && - section.category === expandedSection?.category - } - > - handleSetExpandedSection(section)} > - handleSetExpandedSection(section)} + onMouseEnter={() => handleSetExpandedSection(section)} > - - - - {section.category} - - - {!section.standalone && - section.subsections.map(section => ( - - - {section.title} - - ))} - - {cfg.edition === 'oss' && } - {cfg.edition === 'community' && } - - -
+ + + + {section.category} + + + {!section.standalone && + section.subsections.map(subsection => ( + { + e.preventDefault(); + handleNavigation(subsection.route); + }} + > + + {subsection.title} + + ))} + + {cfg.edition === 'oss' && } + {cfg.edition === 'community' && } + + +
+ ))}
@@ -356,3 +451,10 @@ const SubText = styled(Text)` color: ${props => props.theme.colors.text.disabled}; font-size: ${props => props.theme.fontSizes[1]}px; `; + +const Divider = styled.div` + z-index: ${zIndexMap.sideNavButtons}; + height: 1px; + background: ${props => props.theme.colors.interactive.tonal.neutral[1]}; + width: 60px; +`; diff --git a/web/packages/teleport/src/Navigation/SideNavigation/Section.tsx b/web/packages/teleport/src/Navigation/SideNavigation/Section.tsx index dab21250a8ae0..ab4bd8efc6cab 100644 --- a/web/packages/teleport/src/Navigation/SideNavigation/Section.tsx +++ b/web/packages/teleport/src/Navigation/SideNavigation/Section.tsx @@ -62,16 +62,14 @@ export function Section({ const rightPanelWidth = '236px'; -export const RightPanel = styled(Box).attrs({ pt: 2, px: 2 })<{ +export const RightPanel = styled(Box).attrs({ pt: 2, px: '5px' })<{ isVisible: boolean; skipAnimation: boolean; }>` position: fixed; left: var(--sidenav-width); height: 100%; - scrollbar-gutter: auto; scrollbar-color: ${p => p.theme.colors.spotBackground[2]} transparent; - overflow: visible; width: ${rightPanelWidth}; background: ${p => p.theme.colors.levels.surface}; z-index: ${zIndexMap.sideNavExpandedPanel}; @@ -94,18 +92,14 @@ export const RightPanel = styled(Box).attrs({ pt: 2, px: 2 })<{ top: ${p => p.theme.topBarHeight[1]}px; padding-bottom: ${p => p.theme.topBarHeight[1] + p.theme.space[2]}px; } - @media screen and (min-width: ${p => p.theme.breakpoints.large}px) { - top: ${p => p.theme.topBarHeight[2]}px; - padding-bottom: ${p => p.theme.topBarHeight[3] + p.theme.space[2]}px; - } `; export const CategoryButton = styled.button<{ $active: boolean; isExpanded: boolean; }>` - height: 60px; - width: 60px; + min-height: 60px; + min-width: 60px; cursor: pointer; outline: hidden; border: none; @@ -115,6 +109,11 @@ export const CategoryButton = styled.button<{ justify-content: center; border-radius: ${props => props.theme.radii[2]}px; z-index: ${zIndexMap.sideNavButtons}; + display: flex; + align-items: center; + justify-content: center; + gap: ${props => props.theme.space[1]}px; + font-family: ${props => props.theme.font}; font-size: ${props => props.theme.typography.body4.fontSize}; font-weight: ${props => props.theme.typography.body4.fontWeight}; @@ -177,14 +176,22 @@ export function SubsectionItem({ to, exact, children, + onClick, }: { $active: boolean; to: string; exact: boolean; children: React.ReactNode; + onClick?: (event: React.MouseEvent) => void; }) { return ( - + {children} ); @@ -214,6 +221,9 @@ export function getSubsectionStyles(theme: Theme, active: boolean) { return css` color: ${theme.colors.brand}; background: ${theme.colors.interactive.tonal.primary[0]}; + p { + font-weight: 500; + } &:focus-visible { outline: 2px solid ${theme.colors.interactive.solid.primary.default}; } diff --git a/web/packages/teleport/src/Navigation/SideNavigation/categories.ts b/web/packages/teleport/src/Navigation/SideNavigation/categories.ts index 33d6cf290e40a..f1692be3df123 100644 --- a/web/packages/teleport/src/Navigation/SideNavigation/categories.ts +++ b/web/packages/teleport/src/Navigation/SideNavigation/categories.ts @@ -42,7 +42,6 @@ export const NAVIGATION_CATEGORIES = [ ]; export const STANDALONE_CATEGORIES = [ - NavigationCategory.AddNew, // TODO(rudream): Remove this once shortcuts to pinned/nodes/apps/dbs/desktops/kubes are implemented. NavigationCategory.Resources, ]; diff --git a/web/packages/teleport/src/Navigation/SideNavigation/zIndexMap.ts b/web/packages/teleport/src/Navigation/SideNavigation/zIndexMap.ts index a876bddbe8d37..745f119349d4c 100644 --- a/web/packages/teleport/src/Navigation/SideNavigation/zIndexMap.ts +++ b/web/packages/teleport/src/Navigation/SideNavigation/zIndexMap.ts @@ -17,8 +17,8 @@ */ export const zIndexMap = { - topBar: 23, - sideNavButtons: 22, - sideNavContainer: 21, - sideNavExpandedPanel: 20, + topBar: 9, + sideNavButtons: 8, + sideNavContainer: 7, + sideNavExpandedPanel: 6, }; diff --git a/web/packages/teleport/src/Notifications/Notification.story.tsx b/web/packages/teleport/src/Notifications/Notification.story.tsx index 05b76bfae920b..d8619f66a8227 100644 --- a/web/packages/teleport/src/Notifications/Notification.story.tsx +++ b/web/packages/teleport/src/Notifications/Notification.story.tsx @@ -220,7 +220,7 @@ const ListComponent = () => { css={` width: 100%; justify-content: center; - height: ${p => p.theme.topBarHeight[2]}px; + height: ${p => p.theme.topBarHeight[1]}px; `} > diff --git a/web/packages/teleport/src/TopBar/TopBarSideNav.tsx b/web/packages/teleport/src/TopBar/TopBarSideNav.tsx index a3a5d966ce2b6..c787f984fa763 100644 --- a/web/packages/teleport/src/TopBar/TopBarSideNav.tsx +++ b/web/packages/teleport/src/TopBar/TopBarSideNav.tsx @@ -85,9 +85,6 @@ export const TopBarContainer = styled(TopNav)` @media screen and (min-width: ${p => p.theme.breakpoints.small}px) { height: ${p => p.theme.topBarHeight[1]}px; } - @media screen and (min-width: ${p => p.theme.breakpoints.large}px) { - height: ${p => p.theme.topBarHeight[2]}px; - } `; const TeleportLogo = ({ CustomLogo }: TopBarProps) => { diff --git a/web/packages/teleport/src/components/Dropdown/Dropdown.tsx b/web/packages/teleport/src/components/Dropdown/Dropdown.tsx index 546dccd7c8dcc..36e47071dcfe7 100644 --- a/web/packages/teleport/src/components/Dropdown/Dropdown.tsx +++ b/web/packages/teleport/src/components/Dropdown/Dropdown.tsx @@ -50,9 +50,6 @@ export const Dropdown = styled.div` @media screen and (min-width: ${p => p.theme.breakpoints.small}px) { top: ${p => p.theme.topBarHeight[1]}px; } - @media screen and (min-width: ${p => p.theme.breakpoints.large}px) { - top: ${p => p.theme.topBarHeight[2]}px; - } `; export const DropdownItem = styled.div<{ diff --git a/web/packages/teleport/src/features.tsx b/web/packages/teleport/src/features.tsx index 0ea8bd7b9d3c1..807b257f11cf8 100644 --- a/web/packages/teleport/src/features.tsx +++ b/web/packages/teleport/src/features.tsx @@ -19,7 +19,6 @@ import React from 'react'; import { - AddCircle, Bots as BotsIcon, CirclePlay, ClipboardUser, @@ -29,14 +28,14 @@ import { Laptop, ListAddCheck, ListThin, - Lock, + LockKey, + PlugsConnected, Question, Server, - ShieldCheck, SlidersVertical, Terminal, UserCircleGear, - Users as UsersIcon, + User as UserIcon, } from 'design/Icon'; import cfg from 'teleport/config'; @@ -221,7 +220,7 @@ export class FeatureUsers implements TeleportFeature { navigationItem = { title: NavTitle.Users, - icon: UsersIcon, + icon: UserIcon, exact: true, getLink() { return cfg.getUsersRoute(); @@ -268,13 +267,12 @@ export class FeatureBots implements TeleportFeature { export class FeatureAddBots implements TeleportFeature { category = NavigationCategory.Management; section = ManagementSection.Access; - sideNavCategory = SideNavigationCategory.Access; - hideFromNavigation = true; + sideNavCategory = SideNavigationCategory.AddNew; route = { - title: 'New Bot', + title: 'Bot', path: cfg.routes.botsNew, - exact: false, + exact: true, component: () => , }; @@ -285,6 +283,16 @@ export class FeatureAddBots implements TeleportFeature { getRoute() { return this.route; } + + navigationItem = { + title: NavTitle.NewBot, + icon: BotsIcon, + exact: true, + getLink() { + return cfg.getBotsNewRoute(); + }, + searchableTags: ['add bot', 'new bot', 'bots'], + }; } export class FeatureRoles implements TeleportFeature { @@ -332,7 +340,7 @@ export class FeatureAuthConnectors implements TeleportFeature { navigationItem = { title: NavTitle.AuthConnectors, - icon: ShieldCheck, + icon: PlugsConnected, exact: false, getLink() { return cfg.routes.sso; @@ -359,7 +367,7 @@ export class FeatureLocks implements TeleportFeature { navigationItem = { title: NavTitle.SessionAndIdentityLocks, - icon: Lock, + icon: LockKey, exact: false, getLink() { return cfg.getLocksRoute(); @@ -394,7 +402,7 @@ export class FeatureDiscover implements TeleportFeature { standalone = true; route = { - title: 'Enroll New Resource', + title: 'Resource', path: cfg.routes.discover, exact: true, component: Discover, @@ -402,7 +410,7 @@ export class FeatureDiscover implements TeleportFeature { navigationItem = { title: NavTitle.EnrollNewResource, - icon: AddCircle, + icon: Server, exact: true, getLink() { return cfg.routes.discover; @@ -453,11 +461,10 @@ export class FeatureIntegrations implements TeleportFeature { export class FeatureIntegrationEnroll implements TeleportFeature { category = NavigationCategory.Management; section = ManagementSection.Access; - sideNavCategory = SideNavigationCategory.Access; - parent = FeatureIntegrations; + sideNavCategory = SideNavigationCategory.AddNew; route = { - title: 'Enroll New Integration', + title: 'Integration', path: cfg.routes.integrationEnroll, exact: false, component: () => , @@ -469,7 +476,7 @@ export class FeatureIntegrationEnroll implements TeleportFeature { navigationItem = { title: NavTitle.EnrollNewIntegration, - icon: AddCircle, + icon: IntegrationsIcon, getLink() { return cfg.getIntegrationEnrollRoute(null); }, @@ -668,17 +675,18 @@ export function getOSSFeatures(): TeleportFeature[] { // TODO(rudream): Implement shortcuts to pinned/nodes/apps/dbs/desktops/kubes. new FeatureUnifiedResources(), - // Management + // AddNew + new FeatureDiscover(), + new FeatureIntegrationEnroll(), + new FeatureAddBots(), // - Access new FeatureUsers(), - new FeatureRoles(), new FeatureBots(), - new FeatureAddBots(), new FeatureJoinTokens(), + new FeatureRoles(), new FeatureAuthConnectors(), new FeatureIntegrations(), - new FeatureIntegrationEnroll(), new FeatureClusters(), new FeatureTrust(), @@ -693,8 +701,6 @@ export function getOSSFeatures(): TeleportFeature[] { new FeatureRecordings(), new FeatureSessions(), - new FeatureDiscover(), - // Other new FeatureAccount(), new FeatureHelpAndSupport(), diff --git a/web/packages/teleport/src/types.ts b/web/packages/teleport/src/types.ts index 2644288375b82..2c3620e6d86b7 100644 --- a/web/packages/teleport/src/types.ts +++ b/web/packages/teleport/src/types.ts @@ -63,12 +63,14 @@ export enum NavTitle { // Access Management Users = 'Users', Bots = 'Bots', - Roles = 'User Roles', + Roles = 'Roles', JoinTokens = 'Join Tokens', AuthConnectors = 'Auth Connectors', Integrations = 'Integrations', - EnrollNewResource = 'Enroll New Resource', - EnrollNewIntegration = 'Enroll New Integration', + EnrollNewResource = 'Resource', + EnrollNewIntegration = 'Integration', + NewAccessList = 'Access List', + NewBot = 'Bot', // Identity Governance & Security AccessLists = 'Access Lists',