From 19dd7aad89a6bb715f21fd831d5cf761fdb84891 Mon Sep 17 00:00:00 2001 From: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> Date: Tue, 1 Mar 2022 07:50:46 -0800 Subject: [PATCH] Fix Kbar in operator mode (#1638) Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> --- portal-ui/src/screens/Console/CommandBar.tsx | 218 ++++++++++++++++ portal-ui/src/screens/Console/ConsoleKBar.tsx | 235 +----------------- .../src/screens/Console/kbar-actions.tsx | 63 +++++ portal-ui/src/screens/Console/valid-routes.ts | 27 +- 4 files changed, 295 insertions(+), 248 deletions(-) create mode 100644 portal-ui/src/screens/Console/CommandBar.tsx create mode 100644 portal-ui/src/screens/Console/kbar-actions.tsx diff --git a/portal-ui/src/screens/Console/CommandBar.tsx b/portal-ui/src/screens/Console/CommandBar.tsx new file mode 100644 index 000000000..3e8dccf07 --- /dev/null +++ b/portal-ui/src/screens/Console/CommandBar.tsx @@ -0,0 +1,218 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, 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 . +import * as React from "react"; +import { + ActionId, + ActionImpl, + KBarAnimator, + KBarPortal, + KBarPositioner, + KBarResults, + KBarSearch, + useMatches, + useRegisterActions, +} from "kbar"; +import { Action } from "kbar/lib/types"; +import { Theme } from "@mui/material/styles"; +import makeStyles from "@mui/styles/makeStyles"; +import { routesAsKbarActions } from "./kbar-actions"; + +const useStyles = makeStyles((theme: Theme) => ({ + resultItem: { + display: "flex", + gap: "8px", + alignItems: "center", + fontSize: 14, + "& .min-icon": { + color: theme.palette.primary.main, + width: "18px", + height: "18px", + }, + }, +})); + +const searchStyle = { + padding: "12px 16px", + fontSize: "16px", + width: "100%", + boxSizing: "border-box" as React.CSSProperties["boxSizing"], + outline: "none", + border: "none", + background: "transparent", + color: "#111111", +}; + +const animatorStyle = { + maxWidth: "600px", + width: "100%", + background: "white", + color: "black", + borderRadius: "8px", + overflow: "hidden", + boxShadow: "rgba(0, 0, 0, 0.2) 0px 6px 20px 0px", +}; + +const groupNameStyle = { + padding: "8px 16px", + fontSize: "10px", + textTransform: "uppercase" as const, + opacity: 0.5, +}; + +const CommandBar = ({ + features, + operatorMode, +}: { + operatorMode: boolean; + features: string[] | null; +}) => { + const initialActions: Action[] = routesAsKbarActions(features, operatorMode); + + useRegisterActions(initialActions, [operatorMode]); + + return ( + + + + + + + + + ); +}; + +function RenderResults() { + const { results, rootActionId } = useMatches(); + + return ( + + typeof item === "string" ? ( +
{item}
+ ) : ( + + ) + } + /> + ); +} + +const ResultItem = React.forwardRef( + ( + { + action, + active, + currentRootActionId, + }: { + action: ActionImpl; + active: boolean; + currentRootActionId: ActionId; + }, + ref: React.Ref + ) => { + const classes = useStyles(); + const ancestors = React.useMemo(() => { + if (!currentRootActionId) return action.ancestors; + const index = action.ancestors.findIndex( + (ancestor) => ancestor.id === currentRootActionId + ); + // +1 removes the currentRootAction; e.g. + // if we are on the "Set theme" parent action, + // the UI should not display "Set theme… > Dark" + // but rather just "Dark" + return action.ancestors.slice(index + 1); + }, [action.ancestors, currentRootActionId]); + + return ( +
+
+ {action.icon && action.icon} +
+
+ {ancestors.length > 0 && + ancestors.map((ancestor) => ( + + + {ancestor.name} + + + › + + + ))} + {action.name} +
+ {action.subtitle && ( + {action.subtitle} + )} +
+
+ {action.shortcut?.length ? ( +
+ {action.shortcut.map((sc) => ( + + {sc} + + ))} +
+ ) : null} +
+ ); + } +); + +export default CommandBar; diff --git a/portal-ui/src/screens/Console/ConsoleKBar.tsx b/portal-ui/src/screens/Console/ConsoleKBar.tsx index 5630b0bc1..111320524 100644 --- a/portal-ui/src/screens/Console/ConsoleKBar.tsx +++ b/portal-ui/src/screens/Console/ConsoleKBar.tsx @@ -14,68 +14,11 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import * as React from "react"; -import history from "../../history"; -import { - ActionId, - ActionImpl, - KBarAnimator, - KBarPortal, - KBarPositioner, - KBarProvider, - KBarResults, - KBarSearch, - useMatches, -} from "kbar"; +import { KBarProvider } from "kbar"; import Console from "./Console"; -import { validRoutes } from "./valid-routes"; import { AppState } from "../../store"; import { connect } from "react-redux"; -import { Action } from "kbar/lib/types"; -import { Theme } from "@mui/material/styles"; -import makeStyles from "@mui/styles/makeStyles"; -import { BucketsIcon } from "../../icons"; - -const useStyles = makeStyles((theme: Theme) => ({ - resultItem: { - display: "flex", - gap: "8px", - alignItems: "center", - fontSize: 14, - "& .min-icon": { - color: theme.palette.primary.main, - width: "18px", - height: "18px", - }, - }, -})); - -const searchStyle = { - padding: "12px 16px", - fontSize: "16px", - width: "100%", - boxSizing: "border-box" as React.CSSProperties["boxSizing"], - outline: "none", - border: "none", - background: "transparent", - color: "#111111", -}; - -const animatorStyle = { - maxWidth: "600px", - width: "100%", - background: "white", - color: "black", - borderRadius: "8px", - overflow: "hidden", - boxShadow: "rgba(0, 0, 0, 0.2) 0px 6px 20px 0px", -}; - -const groupNameStyle = { - padding: "8px 16px", - fontSize: "10px", - textTransform: "uppercase" as const, - opacity: 0.5, -}; +import CommandBar from "./CommandBar"; const ConsoleKBar = ({ features, @@ -89,190 +32,18 @@ const ConsoleKBar = ({ return ; } - const allowedMenuItems = validRoutes(features, operatorMode); - - const initialActions = []; - for (const i of allowedMenuItems) { - if (i.children && i.children.length > 0) { - for (const childI of i.children) { - const a: Action = { - id: `${childI.id}`, - name: childI.name, - section: i.name, - perform: () => history.push(`${childI.to}`), - icon: , - }; - initialActions.push(a); - } - } else { - const a: Action = { - id: `${i.id}`, - name: i.name, - section: "Navigation", - perform: () => history.push(`${i.to}`), - icon: , - }; - initialActions.push(a); - } - } - if (!operatorMode) { - // Add additional actions - const a: Action = { - id: `create-bucket`, - name: "Create Bucket", - section: "Buckets", - perform: () => history.push(`/add-bucket`), - icon: , - }; - initialActions.push(a); - } - return ( - + ); }; -function CommandBar() { - return ( - - - - - - - - - ); -} - -function RenderResults() { - const { results, rootActionId } = useMatches(); - - return ( - - typeof item === "string" ? ( -
{item}
- ) : ( - - ) - } - /> - ); -} - -const ResultItem = React.forwardRef( - ( - { - action, - active, - currentRootActionId, - }: { - action: ActionImpl; - active: boolean; - currentRootActionId: ActionId; - }, - ref: React.Ref - ) => { - const classes = useStyles(); - const ancestors = React.useMemo(() => { - if (!currentRootActionId) return action.ancestors; - const index = action.ancestors.findIndex( - (ancestor) => ancestor.id === currentRootActionId - ); - // +1 removes the currentRootAction; e.g. - // if we are on the "Set theme" parent action, - // the UI should not display "Set theme… > Dark" - // but rather just "Dark" - return action.ancestors.slice(index + 1); - }, [action.ancestors, currentRootActionId]); - - return ( -
-
- {action.icon && action.icon} -
-
- {ancestors.length > 0 && - ancestors.map((ancestor) => ( - - - {ancestor.name} - - - › - - - ))} - {action.name} -
- {action.subtitle && ( - {action.subtitle} - )} -
-
- {action.shortcut?.length ? ( -
- {action.shortcut.map((sc) => ( - - {sc} - - ))} -
- ) : null} -
- ); - } -); - const mapState = (state: AppState) => ({ operatorMode: state.system.operatorMode, features: state.console.session.features, diff --git a/portal-ui/src/screens/Console/kbar-actions.tsx b/portal-ui/src/screens/Console/kbar-actions.tsx new file mode 100644 index 000000000..3d0ec6cc1 --- /dev/null +++ b/portal-ui/src/screens/Console/kbar-actions.tsx @@ -0,0 +1,63 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, 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 . + +import { Action } from "kbar/lib/types"; +import history from "../../history"; +import { BucketsIcon } from "../../icons"; +import { validRoutes } from "./valid-routes"; + +export const routesAsKbarActions = ( + features: string[] | null, + operatorMode: boolean +) => { + const initialActions: Action[] = []; + const allowedMenuItems = validRoutes(features, operatorMode); + for (const i of allowedMenuItems) { + if (i.children && i.children.length > 0) { + for (const childI of i.children) { + const a: Action = { + id: `${childI.id}`, + name: childI.name, + section: i.name, + perform: () => history.push(`${childI.to}`), + icon: , + }; + initialActions.push(a); + } + } else { + const a: Action = { + id: `${i.id}`, + name: i.name, + section: "Navigation", + perform: () => history.push(`${i.to}`), + icon: , + }; + initialActions.push(a); + } + } + if (!operatorMode) { + // Add additional actions + const a: Action = { + id: `create-bucket`, + name: "Create Bucket", + section: "Buckets", + perform: () => history.push(`/add-bucket`), + icon: , + }; + initialActions.push(a); + } + return initialActions; +}; diff --git a/portal-ui/src/screens/Console/valid-routes.ts b/portal-ui/src/screens/Console/valid-routes.ts index 5bb708cf5..f6a42cafc 100644 --- a/portal-ui/src/screens/Console/valid-routes.ts +++ b/portal-ui/src/screens/Console/valid-routes.ts @@ -32,6 +32,7 @@ import { GroupsMenuIcon, HealthMenuIcon, IdentityMenuIcon, + InspectMenuIcon, LogsMenuIcon, MetricsMenuIcon, MonitoringMenuIcon, @@ -39,7 +40,6 @@ import { SupportMenuIcon, TraceMenuIcon, UsersMenuIcon, - InspectMenuIcon, } from "../../icons/SidebarMenus"; import { hasPermission } from "../../common/SecureComponent"; import WatchIcon from "../../icons/WatchIcon"; @@ -59,7 +59,6 @@ export const validRoutes = ( operatorMode: boolean ) => { const ldapIsEnabled = (features && features.includes("ldap-idp")) || false; - let consoleMenus: IMenuItem[] = [ { name: "Buckets", @@ -159,13 +158,6 @@ export const validRoutes = ( icon: DrivesMenuIcon, component: NavLink, }, - { - name: "Inspect", - id: "inspectObjects", - to: IAM_PAGES.TOOLS_INSPECT, - icon: InspectMenuIcon, - component: NavLink, - }, ], }, { @@ -202,13 +194,13 @@ export const validRoutes = ( // icon: CallHomeMenuIcon, // to: IAM_PAGES.CALL_HOME, // }, - // { - // name: "Inspect", - // id: "inspect", - // component: NavLink, - // icon: InspectMenuIcon, - // to: IAM_PAGES.TOOLS_WATCH, - // }, + { + name: "Inspect", + id: "inspectObjects", + to: IAM_PAGES.TOOLS_INSPECT, + icon: InspectMenuIcon, + component: NavLink, + }, // { // name: "Profile", // id: "profile", @@ -277,6 +269,7 @@ export const validRoutes = ( { group: "Operator", type: "item", + id: "Tenants", component: NavLink, to: IAM_PAGES.TENANTS, name: "Tenants", @@ -286,6 +279,7 @@ export const validRoutes = ( { group: "Operator", type: "item", + id: "License", component: NavLink, to: IAM_PAGES.LICENSE, name: "License", @@ -295,6 +289,7 @@ export const validRoutes = ( { group: "Operator", type: "item", + id: "Documentation", component: NavLink, to: IAM_PAGES.DOCUMENTATION, name: "Documentation",