From 52212946a2aaa955dd3dadcebee257e85c26d928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Schw=C3=B6rer?= Date: Fri, 9 Dec 2022 15:25:34 +0100 Subject: [PATCH] feat: restructure instance detail page Co-authored-by: Julian Geywitz --- web/src/components/Card/Card.module.scss | 28 +-- web/src/components/Card/Card.tsx | 18 +- .../components/CardGrid/CardGrid.module.scss | 12 -- web/src/components/CardGrid/CardGrid.tsx | 26 --- .../InstanceBanner/InstanceBanner.module.scss | 47 +++++ .../InstanceBanner/InstanceBanner.tsx | 79 ++++++++ web/src/components/JobStartedAt.tsx | 4 +- .../QuickInfoBanner.module.scss | 7 +- web/src/views/Dashboard/Dashboard.module.scss | 5 + web/src/views/Dashboard/Dashboard.tsx | 46 ++--- .../InstanceDetail/InstanceDetail.module.scss | 40 ++--- .../views/InstanceDetail/InstanceDetail.tsx | 168 ++++-------------- web/src/views/JobDetail/JobDetail.module.scss | 15 ++ web/src/views/JobDetail/JobDetail.tsx | 31 ++-- 14 files changed, 245 insertions(+), 281 deletions(-) delete mode 100644 web/src/components/CardGrid/CardGrid.module.scss delete mode 100644 web/src/components/CardGrid/CardGrid.tsx create mode 100644 web/src/components/InstanceBanner/InstanceBanner.module.scss create mode 100644 web/src/components/InstanceBanner/InstanceBanner.tsx diff --git a/web/src/components/Card/Card.module.scss b/web/src/components/Card/Card.module.scss index 560a45c..2b5103c 100644 --- a/web/src/components/Card/Card.module.scss +++ b/web/src/components/Card/Card.module.scss @@ -1,30 +1,12 @@ .root { - display: grid; - grid-template-columns: 24px 1fr min-content; - grid-template-areas: "icon title badge"; - gap: 1rem; padding: 1rem; border: 1px solid var(--color-separator); - border-radius: 4px; + border-radius: 0.5rem; - &.hasBody { - grid-template-areas: "icon title badge" ". body body"; - } - - &:not(:last-of-type) { + > header { + display: flex; + gap: 1rem; + font-weight: bold; margin-bottom: 1rem; } - - .icon { - opacity: 0.65; - } - - .icon, - .title { - align-self: center; - } - - .body { - grid-area: body; - } } diff --git a/web/src/components/Card/Card.tsx b/web/src/components/Card/Card.tsx index ccf4646..2dcd7c3 100644 --- a/web/src/components/Card/Card.tsx +++ b/web/src/components/Card/Card.tsx @@ -1,5 +1,4 @@ import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; -import classNames from "classnames"; import React, { PropsWithChildren, ReactElement } from "react"; import Icon from "../Icon/Icon"; import styles from "./Card.module.scss"; @@ -7,25 +6,20 @@ import styles from "./Card.module.scss"; type Props = { icon: IconDefinition; title: string; - badge?: ReactElement; }; function Card({ icon, - badge, title, children, }: PropsWithChildren): ReactElement { return ( -
- - {title} - {badge} - {children &&
{children}
} +
+
+ + {title} +
+
{children}
); } diff --git a/web/src/components/CardGrid/CardGrid.module.scss b/web/src/components/CardGrid/CardGrid.module.scss deleted file mode 100644 index 3c80d87..0000000 --- a/web/src/components/CardGrid/CardGrid.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -.root { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); - gap: 2rem; - - > section { - > h2 { - font-size: 1em; - padding: 0.5rem 0; - } - } -} diff --git a/web/src/components/CardGrid/CardGrid.tsx b/web/src/components/CardGrid/CardGrid.tsx deleted file mode 100644 index 8c832d3..0000000 --- a/web/src/components/CardGrid/CardGrid.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { PropsWithChildren, ReactElement } from "react"; -import styles from "./CardGrid.module.scss"; - -function CardGrid({ children }: PropsWithChildren): ReactElement { - return
{children}
; -} - -type SectionProps = { - headline: string; -}; - -function Section({ - children, - headline, -}: PropsWithChildren): ReactElement { - return ( -
-

{headline}

- {children} -
- ); -} - -CardGrid.Section = Section; - -export default CardGrid; diff --git a/web/src/components/InstanceBanner/InstanceBanner.module.scss b/web/src/components/InstanceBanner/InstanceBanner.module.scss new file mode 100644 index 0000000..b1fda5b --- /dev/null +++ b/web/src/components/InstanceBanner/InstanceBanner.module.scss @@ -0,0 +1,47 @@ +.root { + .divider { + background: var(--color-separator); + width: 1px; + } + + .specifications { + display: grid; + grid-template-columns: 1fr auto 1fr; + gap: 1rem 2rem; + background: var(--overlay); + padding: 1rem; + border-radius: 0.5rem 0.5rem 0 0; + } + + > footer { + display: flex; + justify-content: space-between; + padding: 1rem; + border: 1px solid var(--color-separator); + border-top: 0; + border-radius: 0 0 0.5rem 0.5rem; + } + + .nodes { + > h3 { + font-size: 1rem; + margin: 0 0 1rem; + } + + .node { + display: flex; + justify-content: space-between; + padding: 0.5rem 0; + + &:not(:last-child) { + border-bottom: 1px solid var(--color-separator); + } + } + } + + .tags, + .osName { + display: flex; + gap: 0.5rem; + } +} diff --git a/web/src/components/InstanceBanner/InstanceBanner.tsx b/web/src/components/InstanceBanner/InstanceBanner.tsx new file mode 100644 index 0000000..271fa67 --- /dev/null +++ b/web/src/components/InstanceBanner/InstanceBanner.tsx @@ -0,0 +1,79 @@ +import { + faComputer, + faHdd, + faMemory, + faMicrochip, + faTags, +} from "@fortawesome/free-solid-svg-icons"; +import React from "react"; +import { GntInstance } from "../../api/models"; +import { prettyPrintMiB } from "../../helpers"; +import Icon from "../Icon/Icon"; +import PrefixLink from "../PrefixLink"; +import QuickInfoBanner from "../QuickInfoBanner/QuickInfoBanner"; +import StatusBadge, { BadgeStatus } from "../StatusBadge/StatusBadge"; +import styles from "./InstanceBanner.module.scss"; + +type Props = { + instance: GntInstance; +}; + +function InstanceBanner({ instance }: Props) { + const totalStorage = instance.disks + .map(({ capacity }) => capacity) + .reduce((prev, cur) => prev + cur); + + return ( +
+
+ + + + + +
+
+

Nodes

+
+ + {instance.primaryNode} + + + Primary +
+ {instance.secondaryNodes.map((node) => ( +
+ {node} +
+ ))} +
+
+
+
+ + {instance.OS} +
+
+ + {instance.tags.map((tag) => ( + {tag} + ))} +
+
+
+ ); +} + +export default InstanceBanner; diff --git a/web/src/components/JobStartedAt.tsx b/web/src/components/JobStartedAt.tsx index ff703af..2188a59 100644 --- a/web/src/components/JobStartedAt.tsx +++ b/web/src/components/JobStartedAt.tsx @@ -12,7 +12,9 @@ function JobStartedAt({ timestamp }: Props): ReactElement { const date = unixToDate(timestamp); - return {date.toLocaleTimeString()}; + return ( + [{date.toLocaleTimeString()}] + ); } export default JobStartedAt; diff --git a/web/src/components/QuickInfoBanner/QuickInfoBanner.module.scss b/web/src/components/QuickInfoBanner/QuickInfoBanner.module.scss index 1041402..177fec8 100644 --- a/web/src/components/QuickInfoBanner/QuickInfoBanner.module.scss +++ b/web/src/components/QuickInfoBanner/QuickInfoBanner.module.scss @@ -1,13 +1,14 @@ .root { display: flex; - justify-content: space-around; - background: var(--overlay); + justify-content: space-between; + align-items: center; + height: 100%; > div { display: inline-flex; flex-direction: column; align-items: center; - padding: 2rem; + padding: 0 1rem; .value { display: block; diff --git a/web/src/views/Dashboard/Dashboard.module.scss b/web/src/views/Dashboard/Dashboard.module.scss index 12867a6..c1de9d2 100644 --- a/web/src/views/Dashboard/Dashboard.module.scss +++ b/web/src/views/Dashboard/Dashboard.module.scss @@ -11,3 +11,8 @@ font-weight: bold; } } + +.clusterSpecifications { + padding: 2rem; + background: var(--overlay); +} diff --git a/web/src/views/Dashboard/Dashboard.tsx b/web/src/views/Dashboard/Dashboard.tsx index 53e8f72..c71031a 100644 --- a/web/src/views/Dashboard/Dashboard.tsx +++ b/web/src/views/Dashboard/Dashboard.tsx @@ -38,28 +38,30 @@ function Dashboard(): ReactElement { {...apiProps} render={({ master, nodes, instances }) => ( <> - - - - - - +
+ + + + + + +
Master diff --git a/web/src/views/InstanceDetail/InstanceDetail.module.scss b/web/src/views/InstanceDetail/InstanceDetail.module.scss index ccc31de..61e6fca 100644 --- a/web/src/views/InstanceDetail/InstanceDetail.module.scss +++ b/web/src/views/InstanceDetail/InstanceDetail.module.scss @@ -4,37 +4,27 @@ align-items: center; } -.diskCapacity { - font-size: 1.25em; - margin: 0; +.cards { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(480px, 1fr)); + gap: 2rem; + margin-top: 2rem; } -.nicVlan { - margin: 0.5rem 0; -} - -.nicMac { - color: var(--color-emphasis-low); - margin: 0; -} +.nic, +.disk { + padding: 0.5rem 0; -.nicBridge { - margin: 0 0 0.25rem; - font-size: 1.25em; -} - -.nicParamList { - list-style: none; - margin: 0; - padding: 0; + > p { + margin: 0.125rem 0; + } - li { - display: flex; - justify-content: space-between; - padding: 0.5rem 0; + > p:last-of-type { + font-family: monospace; + font-size: 1rem; } - li:not(:last-child) { + &:not(:last-child) { border-bottom: 1px solid var(--color-separator); } } diff --git a/web/src/views/InstanceDetail/InstanceDetail.tsx b/web/src/views/InstanceDetail/InstanceDetail.tsx index c69fde0..0a9d1c9 100644 --- a/web/src/views/InstanceDetail/InstanceDetail.tsx +++ b/web/src/views/InstanceDetail/InstanceDetail.tsx @@ -1,23 +1,13 @@ -import { - faComputer, - faEthernet, - faHdd, - faMemory, - faMicrochip, - faNetworkWired, - faServer, - faTag, -} from "@fortawesome/free-solid-svg-icons"; -import React, { PropsWithChildren, ReactElement } from "react"; +import { faHdd, faNetworkWired } from "@fortawesome/free-solid-svg-icons"; +import React, { ReactElement } from "react"; import { useParams } from "react-router-dom"; import { useApi } from "../../api"; -import { GntDisk, GntInstance, GntNic, GntNicInfo } from "../../api/models"; +import { GntInstance } from "../../api/models"; import ApiDataRenderer from "../../components/ApiDataRenderer/ApiDataRenderer"; import Card from "../../components/Card/Card"; -import CardGrid from "../../components/CardGrid/CardGrid"; import ContentWrapper from "../../components/ContentWrapper/ContentWrapper"; import InstanceActions from "../../components/InstanceActions/InstanceActions"; -import QuickInfoBanner from "../../components/QuickInfoBanner/QuickInfoBanner"; +import InstanceBanner from "../../components/InstanceBanner/InstanceBanner"; import StatusBadge, { BadgeStatus, } from "../../components/StatusBadge/StatusBadge"; @@ -25,83 +15,10 @@ import { useClusterName } from "../../helpers/hooks"; import { prettyPrintMiB } from "../../helpers/numbers"; import styles from "./InstanceDetail.module.scss"; -function DiskCard({ name, capacity, template }: GntDisk): ReactElement { - return ( - {template}} - > -

{prettyPrintMiB(capacity)}

-
- ); -} - -function TagCard({ tag }: { tag: string }): ReactElement { - return ; -} - -type NodeCardProps = { - name: string; - primary?: boolean; -}; - -function NodeCard({ name, primary }: NodeCardProps): ReactElement { - return ( - Primary - ) : undefined - } - /> - ); -} - -function NicCard({ name, mode, mac, bridge, vlan }: GntNic): ReactElement { - return ( - {mode}} - > - {bridge.length &&

{bridge}

} - {vlan && ( -

- Vlan: {vlan} -

- )} -

{mac}

-
- ); -} - -function NetworkCard({ - nicType, - nicTypeFriendly: nicFriendlyType, -}: GntNicInfo): ReactElement { - return ( - -
    -
  • - NIC Type - {nicFriendlyType} -
  • -
-
- ); -} - type InstanceResponse = { instance: GntInstance; }; -function OSCard({ os }: { os: string }): ReactElement { - return ; -} - const InstanceDetail = (): ReactElement => { const { instanceName } = useParams<{ instanceName: string }>(); const clusterName = useClusterName(); @@ -115,10 +32,6 @@ const InstanceDetail = (): ReactElement => { {...apiProps} render={({ instance }) => { - const totalStorage = instance.disks - .map(({ capacity }) => capacity) - .reduce((prev, cur) => prev + cur); - const hostname = instance.name.split(".")[0]; return ( @@ -139,55 +52,34 @@ const InstanceDetail = (): ReactElement => { /> - - - - - - - - - {instance.disks.map((disk) => ( - - ))} - - - - {instance.nics.map((nic) => ( - - ))} - - - - {instance.secondaryNodes.map((node) => ( - + + +
+ + {instance.nics.map(({ name, mode, bridge, mac }) => ( +
+

+ {name} +

+

+ {mode}: {mode === "bridged" ? bridge : ""} • {mac} +

+
))} - - - {instance.tags.map((tag) => ( - +
+ + {instance.disks.map(({ name, template, capacity }) => ( +
+

+ {name} +

+

+ {template} • {prettyPrintMiB(capacity)} +

+
))} - - - - - +
+
); }} diff --git a/web/src/views/JobDetail/JobDetail.module.scss b/web/src/views/JobDetail/JobDetail.module.scss index 6543c09..2a795b7 100644 --- a/web/src/views/JobDetail/JobDetail.module.scss +++ b/web/src/views/JobDetail/JobDetail.module.scss @@ -7,3 +7,18 @@ .text { color: var(--color-emphasis-low); } + +.log { + padding: 2rem 0; + + .console { + display: flex; + flex-direction: column; + gap: 0.5rem; + font-family: monospace; + font-size: 1rem; + background: var(--overlay); + padding: 1rem; + border-radius: 0.5rem; + } +} diff --git a/web/src/views/JobDetail/JobDetail.tsx b/web/src/views/JobDetail/JobDetail.tsx index 2167f30..810b7f8 100644 --- a/web/src/views/JobDetail/JobDetail.tsx +++ b/web/src/views/JobDetail/JobDetail.tsx @@ -1,10 +1,7 @@ -import { faWrench } from "@fortawesome/free-solid-svg-icons"; import React, { ReactElement } from "react"; import { useParams } from "react-router-dom"; import { useApi } from "../../api"; -import { GntJobLogEntry, GntJobWithLog } from "../../api/models"; -import Card from "../../components/Card/Card"; -import CardGrid from "../../components/CardGrid/CardGrid"; +import { GntJobWithLog } from "../../api/models"; import ContentWrapper from "../../components/ContentWrapper/ContentWrapper"; import JobStartedAt from "../../components/JobStartedAt"; import JobStatus from "../../components/JobStatus"; @@ -17,16 +14,6 @@ type JobResponse = { job: GntJobWithLog; }; -function LogEntryCard({ message, startedAt }: GntJobLogEntry): ReactElement { - return ( - -
- -
-
- ); -} - export default function JobDetail(): ReactElement { const clusterName = useClusterName(); const { jobID } = useParams<{ jobID: string }>(); @@ -51,11 +38,17 @@ export default function JobDetail(): ReactElement { - - {log.map((entry) => ( - - ))} - +
+

Log

+
+ {log.map((entry) => ( +
+ {" "} + {entry.message} +
+ ))} +
+
); }