|
@@ -1,125 +1,241 @@
|
|
|
import * as React from "react";
|
|
|
-import { INavLink, INavLinkGroup, INavStyles, Nav } from "@fluentui/react";
|
|
|
-import { SizeMe } from "react-sizeme";
|
|
|
+import { IconButton, IContextualMenuItem, INavLinkGroup, Nav, Pivot, PivotItem, Stack, useTheme } from "@fluentui/react";
|
|
|
+import { useConst } from "@fluentui/react-hooks";
|
|
|
import nlsHPCC from "src/nlsHPCC";
|
|
|
-import { useFavorites } from "../hooks/favorite";
|
|
|
-import { hashHistory } from "../util/history";
|
|
|
+import { MainNav, routes } from "../routes";
|
|
|
+import { pushUrl } from "../util/history";
|
|
|
+import { Breadcrumbs } from "./Breadcrumbs";
|
|
|
+import { useFavorites, useHistory } from "src-react/hooks/favorite";
|
|
|
|
|
|
+// Top Level Nav ---
|
|
|
const navLinkGroups: INavLinkGroup[] = [
|
|
|
{
|
|
|
- name: "Favorites",
|
|
|
- links: []
|
|
|
- },
|
|
|
- {
|
|
|
- name: "History",
|
|
|
- links: []
|
|
|
- },
|
|
|
- {
|
|
|
- name: "Home",
|
|
|
- links: [
|
|
|
- { url: "#/activities", name: nlsHPCC.Activities },
|
|
|
- { url: "#/activities/legacy", name: `${nlsHPCC.Activities} (L)` },
|
|
|
- { url: "#/clusters", name: nlsHPCC.TargetClusters },
|
|
|
- { url: "#/events", name: nlsHPCC.EventScheduler }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- name: "ECL",
|
|
|
- links: [
|
|
|
- { url: "#/workunits", name: nlsHPCC.Workunits },
|
|
|
- { url: "#/workunits/dashboard", name: `${nlsHPCC.Workunits} (D)` },
|
|
|
- { url: "#/workunits/legacy", name: `${nlsHPCC.Workunits} (L)` },
|
|
|
- { url: "#/play", name: nlsHPCC.Playground },
|
|
|
- { url: "#/play/legacy", name: `${nlsHPCC.Playground} (L)` },
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- name: "Files",
|
|
|
links: [
|
|
|
- { url: "#/files", name: nlsHPCC.LogicalFiles },
|
|
|
- { url: "#/files/legacy", name: `${nlsHPCC.LogicalFiles} (L)` },
|
|
|
- { url: "#/landingzone", name: nlsHPCC.LandingZones },
|
|
|
- { url: "#/dfuworkunits", name: nlsHPCC.Workunits },
|
|
|
- { url: "#/dfuworkunits/legacy", name: `${nlsHPCC.Workunits} (L)` },
|
|
|
- { url: "#/xref", name: nlsHPCC.XRef },
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- name: "Published Queries",
|
|
|
- links: [
|
|
|
- { url: "#/queries", name: nlsHPCC.Queries },
|
|
|
- { url: "#/queries/legacy", name: `${nlsHPCC.Queries} (L)` },
|
|
|
- { url: "#/packagemaps", name: nlsHPCC.PackageMaps },
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- name: "Operations",
|
|
|
- links: [
|
|
|
- { url: "#/topology", name: nlsHPCC.Topology },
|
|
|
- { url: "#/diskusage", name: nlsHPCC.DiskUsage },
|
|
|
- { url: "#/clusters2", name: nlsHPCC.TargetClusters },
|
|
|
- { url: "#/processes", name: nlsHPCC.ClusterProcesses },
|
|
|
- { url: "#/servers", name: nlsHPCC.SystemServers },
|
|
|
- { url: "#/security", name: nlsHPCC.Security },
|
|
|
- { url: "#/monitoring", name: nlsHPCC.Monitoring },
|
|
|
- { url: "#/esdl", name: nlsHPCC.DESDL },
|
|
|
- { url: "#/elk", name: nlsHPCC.LogVisualization },
|
|
|
+ {
|
|
|
+ name: nlsHPCC.Activities,
|
|
|
+ url: "#/activities",
|
|
|
+ icon: "Home",
|
|
|
+ key: "activities"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: nlsHPCC.ECL,
|
|
|
+ url: "#/workunits",
|
|
|
+ icon: "SetAction",
|
|
|
+ key: "workunits"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: nlsHPCC.Files,
|
|
|
+ url: "#/files",
|
|
|
+ icon: "PageData",
|
|
|
+ key: "files"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: nlsHPCC.PublishedQueries,
|
|
|
+ url: "#/queries",
|
|
|
+ icon: "Globe",
|
|
|
+ key: "queries"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: nlsHPCC.Operations,
|
|
|
+ url: "#/topology",
|
|
|
+ icon: "Admin",
|
|
|
+ key: "topology"
|
|
|
+ }
|
|
|
]
|
|
|
}
|
|
|
];
|
|
|
-navLinkGroups.forEach(group => {
|
|
|
- group.links.forEach(link => {
|
|
|
- link.key = link.url.substr(1);
|
|
|
+
|
|
|
+const navIdx: { [id: string]: MainNav[] } = {};
|
|
|
+
|
|
|
+function append(route, path) {
|
|
|
+ if (!navIdx[path]) {
|
|
|
+ navIdx[path] = [];
|
|
|
+ }
|
|
|
+ route.mainNav?.forEach(item => {
|
|
|
+ navIdx[path].push(item);
|
|
|
});
|
|
|
+}
|
|
|
+
|
|
|
+routes.forEach((route: any) => {
|
|
|
+ if (Array.isArray(route.path)) {
|
|
|
+ route.path.forEach(path => {
|
|
|
+ append(route, path);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ append(route, route.path);
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
-const navStyles = (width: number, height: number): Partial<INavStyles> => {
|
|
|
- return {
|
|
|
- root: {
|
|
|
- width,
|
|
|
- height,
|
|
|
- boxSizing: "border-box",
|
|
|
- border: "1px solid #eee",
|
|
|
- overflow: "auto",
|
|
|
- }
|
|
|
- };
|
|
|
+function navSelectedKey(hashPath) {
|
|
|
+ const rootPath = navIdx[`/${hashPath?.split("/")[1]}`];
|
|
|
+ if (rootPath?.length) {
|
|
|
+ return rootPath[0];
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
+const FIXED_WIDTH = 38;
|
|
|
+
|
|
|
+interface MainNavigationProps {
|
|
|
+ hashPath: string;
|
|
|
+ useDarkMode: boolean;
|
|
|
+ setUseDarkMode: (_: boolean) => void;
|
|
|
+}
|
|
|
+
|
|
|
+export const MainNavigation: React.FunctionComponent<MainNavigationProps> = ({
|
|
|
+ hashPath,
|
|
|
+ useDarkMode,
|
|
|
+ setUseDarkMode
|
|
|
+}) => {
|
|
|
+
|
|
|
+ const theme = useTheme();
|
|
|
+
|
|
|
+ const menu = useConst([...navLinkGroups]);
|
|
|
+
|
|
|
+ const selKey = React.useMemo(() => {
|
|
|
+ return navSelectedKey(hashPath);
|
|
|
+ }, [hashPath]);
|
|
|
+
|
|
|
+ return <Stack verticalAlign="space-between" styles={{ root: { width: `${FIXED_WIDTH}px`, height: "100%", position: "relative", backgroundColor: theme.palette.themeLighterAlt } }}>
|
|
|
+ <Stack.Item>
|
|
|
+ <Nav selectedKey={selKey} groups={menu} />
|
|
|
+ </Stack.Item>
|
|
|
+ <Stack.Item>
|
|
|
+ <IconButton iconProps={{ iconName: useDarkMode ? "Sunny" : "ClearNight" }} onClick={() => setUseDarkMode(!useDarkMode)} />
|
|
|
+ <IconButton iconProps={{ iconName: "Settings" }} onClick={() => { }} />
|
|
|
+ </Stack.Item>
|
|
|
+ </Stack>;
|
|
|
+};
|
|
|
+
|
|
|
+// Second Level Nav ---
|
|
|
+interface SubMenu {
|
|
|
+ headerText: string;
|
|
|
+ itemKey: string;
|
|
|
+}
|
|
|
+
|
|
|
+type SubMenuItems = { [nav: string]: SubMenu[] };
|
|
|
+
|
|
|
+const subMenuItems: SubMenuItems = {
|
|
|
+ "activities": [
|
|
|
+ { headerText: nlsHPCC.Activities, itemKey: "/activities" },
|
|
|
+ { headerText: nlsHPCC.Activities + " (L)", itemKey: "/activities/legacy" },
|
|
|
+ { headerText: nlsHPCC.TargetClusters, itemKey: "/clusters" },
|
|
|
+ { headerText: nlsHPCC.EventScheduler + " (L)", itemKey: "/events" }
|
|
|
+ ],
|
|
|
+ "workunits": [
|
|
|
+ { headerText: nlsHPCC.Workunits, itemKey: "/workunits" },
|
|
|
+ { headerText: nlsHPCC.Dashboard, itemKey: "/workunits/dashboard" },
|
|
|
+ { headerText: nlsHPCC.Workunits + " (L)", itemKey: "/workunits/legacy" },
|
|
|
+ { headerText: nlsHPCC.Playground, itemKey: "/play" },
|
|
|
+ { headerText: nlsHPCC.Playground + " (L)", itemKey: "/play/legacy" },
|
|
|
+ ],
|
|
|
+ "files": [
|
|
|
+ { headerText: nlsHPCC.LogicalFiles, itemKey: "/files" },
|
|
|
+ { headerText: nlsHPCC.LogicalFiles + " (L)", itemKey: "/files/legacy" },
|
|
|
+ { headerText: nlsHPCC.LandingZones, itemKey: "/landingzone" },
|
|
|
+ { headerText: nlsHPCC.Workunits, itemKey: "/dfuworkunits" },
|
|
|
+ { headerText: nlsHPCC.Workunits + " (L)", itemKey: "/dfuworkunits/legacy" },
|
|
|
+ { headerText: nlsHPCC.XRef, itemKey: "/xref" },
|
|
|
+ ],
|
|
|
+ "queries": [
|
|
|
+ { headerText: nlsHPCC.Queries, itemKey: "/queries" },
|
|
|
+ { headerText: nlsHPCC.Queries + " (L)", itemKey: "/queries/legacy" },
|
|
|
+ { headerText: nlsHPCC.PackageMaps, itemKey: "/packagemaps" }
|
|
|
+ ],
|
|
|
+ "topology": [
|
|
|
+ { headerText: nlsHPCC.Topology, itemKey: "/topology" },
|
|
|
+ { headerText: nlsHPCC.DiskUsage, itemKey: "/diskusage" },
|
|
|
+ { headerText: nlsHPCC.TargetClusters, itemKey: "/clusters2" },
|
|
|
+ { headerText: nlsHPCC.ClusterProcesses, itemKey: "/processes" },
|
|
|
+ { headerText: nlsHPCC.SystemServers, itemKey: "/servers" },
|
|
|
+ { headerText: nlsHPCC.Security, itemKey: "/security" },
|
|
|
+ { headerText: nlsHPCC.Monitoring, itemKey: "/monitoring" },
|
|
|
+ { headerText: nlsHPCC.DESDL, itemKey: "/esdl" },
|
|
|
+ { headerText: nlsHPCC.LogVisualization, itemKey: "/elk" },
|
|
|
+ ],
|
|
|
};
|
|
|
|
|
|
-interface DevMenuProps {
|
|
|
- location: string
|
|
|
+const subNavIdx: { [id: string]: string[] } = {};
|
|
|
+
|
|
|
+for (const key in subMenuItems) {
|
|
|
+ const subNav = subMenuItems[key];
|
|
|
+ subNav.forEach(item => {
|
|
|
+ if (!subNavIdx[item.itemKey]) {
|
|
|
+ subNavIdx[item.itemKey] = [];
|
|
|
+ }
|
|
|
+ subNavIdx[item.itemKey].push(key);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function subNavSelectedKey(hashPath) {
|
|
|
+ return !!subNavIdx[hashPath] ? hashPath : null;
|
|
|
}
|
|
|
|
|
|
-const FIXED_WIDTH = 240;
|
|
|
+const handleLinkClick = (item?: PivotItem) => {
|
|
|
+ if (item?.props?.itemKey) {
|
|
|
+ pushUrl(item.props.itemKey);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+interface SubNavigationProps {
|
|
|
+ hashPath: string;
|
|
|
+}
|
|
|
|
|
|
-export const DevMenu: React.FunctionComponent<DevMenuProps> = ({
|
|
|
- location
|
|
|
+export const SubNavigation: React.FunctionComponent<SubNavigationProps> = ({
|
|
|
+ hashPath,
|
|
|
}) => {
|
|
|
|
|
|
+ const theme = useTheme();
|
|
|
+
|
|
|
const [favorites] = useFavorites();
|
|
|
- const [menu, setMenu] = React.useState<INavLinkGroup[]>([...navLinkGroups]);
|
|
|
+ const [history] = useHistory();
|
|
|
|
|
|
- React.useEffect(() => {
|
|
|
- navLinkGroups[0].links = Object.keys(favorites).map((key): INavLink => {
|
|
|
- return { url: key, name: key };
|
|
|
- });
|
|
|
- setMenu([...navLinkGroups]);
|
|
|
- }, [favorites]);
|
|
|
+ const mainNav = React.useMemo(() => {
|
|
|
+ return navSelectedKey(hashPath);
|
|
|
+ }, [hashPath]);
|
|
|
|
|
|
- React.useEffect(() => {
|
|
|
- return hashHistory.listen((location, action) => {
|
|
|
- navLinkGroups[1].links = hashHistory.recent().map((row): INavLink => {
|
|
|
- return { url: `#${row.pathname + row.search}`, name: row.pathname };
|
|
|
+ const subNav = React.useMemo(() => {
|
|
|
+ return subNavSelectedKey(hashPath);
|
|
|
+ }, [hashPath]);
|
|
|
+
|
|
|
+ const altSubNav = React.useMemo(() => {
|
|
|
+ const parts = hashPath.split("/");
|
|
|
+ parts.shift();
|
|
|
+ return parts.shift();
|
|
|
+ }, [hashPath]);
|
|
|
+
|
|
|
+ const favoriteMenu: IContextualMenuItem[] = React.useMemo(() => {
|
|
|
+ const retVal: IContextualMenuItem[] = [];
|
|
|
+ for (const key in favorites) {
|
|
|
+ retVal.push({
|
|
|
+ name: decodeURI(key),
|
|
|
+ href: key,
|
|
|
+ key,
|
|
|
});
|
|
|
- setMenu([...navLinkGroups]);
|
|
|
- });
|
|
|
- }, []);
|
|
|
-
|
|
|
- return <SizeMe monitorHeight>{({ size }) =>
|
|
|
- <div style={{ width: `${FIXED_WIDTH}px`, height: "100%", position: "relative" }}>
|
|
|
- <div style={{ position: "absolute" }}>
|
|
|
- <Nav groups={menu} selectedKey={location} styles={navStyles(FIXED_WIDTH, size.height)} />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </SizeMe>;
|
|
|
+ }
|
|
|
+ return retVal;
|
|
|
+ }, [favorites]);
|
|
|
+
|
|
|
+ return <div style={{ backgroundColor: theme.palette.themeLighter }}>
|
|
|
+ <Stack horizontal horizontalAlign="space-between">
|
|
|
+ <Stack.Item align="center" grow={1}>
|
|
|
+ <Stack horizontal >
|
|
|
+ <Stack.Item grow={0} >
|
|
|
+ <Pivot selectedKey={subNav || altSubNav} onLinkClick={handleLinkClick} headersOnly={true} linkFormat="tabs" styles={{ root: { marginLeft: 4 }, text: { lineHeight: 20 }, link: { maxHeight: 20, marginRight: 4 }, linkContent: { maxHeight: 20 } }} >
|
|
|
+ {subMenuItems[mainNav]?.map(row => <PivotItem headerText={row.headerText} itemKey={row.itemKey} />)}
|
|
|
+ </Pivot>
|
|
|
+ </Stack.Item>
|
|
|
+ {!subNav &&
|
|
|
+ <Stack.Item grow={1}>
|
|
|
+ <Breadcrumbs hashPath={hashPath} ignoreN={1} />
|
|
|
+ </Stack.Item>
|
|
|
+ }
|
|
|
+ </Stack>
|
|
|
+ </Stack.Item>
|
|
|
+ <Stack.Item align="center" grow={0}>
|
|
|
+ <IconButton title={nlsHPCC.Advanced} iconProps={{ iconName: "History" }} menuProps={{ items: history }} />
|
|
|
+ <IconButton title={nlsHPCC.Advanced} iconProps={{ iconName: favoriteMenu.length === 0 ? "FavoriteStar" : "FavoriteStarFill" }} menuProps={{ items: favoriteMenu }} />
|
|
|
+ </Stack.Item>
|
|
|
+ </Stack>
|
|
|
+ </div>;
|
|
|
};
|