From 956805603e88519cd742052a62f0cc59cd50ca1e Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Sun, 1 Jan 2023 08:00:08 +0000 Subject: [PATCH] get home screen working pretty much --- frontend/src/components/home/Home.tsx | 107 +++++++++++-- .../src/components/home/Tree/AddServer.tsx | 65 ++++++++ frontend/src/components/home/Tree/Builds.tsx | 49 ++++++ .../src/components/home/Tree/Deployment.tsx | 54 +++++++ frontend/src/components/home/Tree/New.tsx | 96 ++++++++++++ frontend/src/components/home/Tree/Server.tsx | 145 ++++++++++++++++++ .../src/components/home/Updates/Update.tsx | 24 ++- frontend/src/components/home/home.module.scss | 93 ++++++++++- frontend/src/style/index.scss | 1 + frontend/src/util/helpers.ts | 8 + 10 files changed, 627 insertions(+), 15 deletions(-) create mode 100644 frontend/src/components/home/Tree/AddServer.tsx create mode 100644 frontend/src/components/home/Tree/Builds.tsx create mode 100644 frontend/src/components/home/Tree/Deployment.tsx create mode 100644 frontend/src/components/home/Tree/New.tsx create mode 100644 frontend/src/components/home/Tree/Server.tsx diff --git a/frontend/src/components/home/Home.tsx b/frontend/src/components/home/Home.tsx index 1bd000a03..04494ae05 100644 --- a/frontend/src/components/home/Home.tsx +++ b/frontend/src/components/home/Home.tsx @@ -1,18 +1,107 @@ -import { Component } from "solid-js"; +import { Component, createMemo, createSignal, For, Match, Switch } from "solid-js"; +import { useAppDimensions } from "../../state/DimensionProvider"; +import { useAppState } from "../../state/StateProvider"; +import { combineClasses } from "../../util/helpers"; +import Input from "../shared/Input"; +import Grid from "../shared/layout/Grid"; +import Tabs from "../shared/tabs/Tabs"; import s from "./home.module.scss" import Summary from "./Summary"; +import AddServer from "./Tree/AddServer"; +import Builds from "./Tree/Builds"; +import Server from "./Tree/Server"; import Updates from "./Updates/Updates"; const Home: Component<{}> = (p) => { + const { servers } = useAppState(); + const { width } = useAppDimensions(); + const [serverFilter, setServerFilter] = createSignal(""); + const serverIDs = createMemo(() => { + if (servers.loaded()) { + const filters = serverFilter() + .split(" ") + .filter((term) => term.length > 0) + .map((term) => term.toLowerCase()); + return servers.ids()?.filter((id) => { + const name = servers.get(id)!.server.name; + for (const term of filters) { + if (!name.includes(term)) { + return false; + } + } + return true; + }); + } else { + return undefined; + } + }); return ( - <> -
- -
-
- -
- + + = 1200}> + + ( + + + {(id) => } + + + ), + }, + { + title: "builds", + element: () => , + }, + ]} + /> + + + + + + + + + + ( + + + {(id) => } + + + ), + }, + { + title: "builds", + element: () => , + }, + ]} + /> + + + + ); } diff --git a/frontend/src/components/home/Tree/AddServer.tsx b/frontend/src/components/home/Tree/AddServer.tsx new file mode 100644 index 000000000..64466e7d7 --- /dev/null +++ b/frontend/src/components/home/Tree/AddServer.tsx @@ -0,0 +1,65 @@ +import { Component, onMount } from "solid-js"; +import { createStore } from "solid-js/store"; +import { client, pushNotification } from "../../.."; +import { useAppState } from "../../../state/StateProvider"; +import { CreateServerBody } from "../../../util/client_types"; +import { useToggle } from "../../../util/hooks"; +import Input from "../../shared/Input"; +import Grid from "../../shared/layout/Grid"; +import CenterMenu from "../../shared/menu/CenterMenu"; + +const AddServer: Component<{}> = () => { + const [show, toggleShow] = useToggle(); + return ( + } + position="center" + /> + ); +}; + +const Content: Component<{ close: () => void }> = (p) => { + const { ws } = useAppState(); + let nameInput: HTMLInputElement | undefined; + const [server, setServer] = createStore({ + name: "", + address: "", + }); + onMount(() => nameInput?.focus()); + const create = async () => { + if (server.name.length > 0 && server.address.length > 0) { + await client.create_server(server); + p.close(); + } else { + pushNotification("bad", "a field is empty. fill in all fields"); + } + }; + return ( + + setServer("name", name)} + placeholder="name" + style={{ "font-size": "1.5rem" }} + /> + setServer("address", address)} + placeholder="address" + style={{ "font-size": "1.5rem" }} + /> + + + ); +}; + +export default AddServer; diff --git a/frontend/src/components/home/Tree/Builds.tsx b/frontend/src/components/home/Tree/Builds.tsx new file mode 100644 index 000000000..cc7d004ce --- /dev/null +++ b/frontend/src/components/home/Tree/Builds.tsx @@ -0,0 +1,49 @@ +import { A } from "@solidjs/router"; +import { Component, For, Show } from "solid-js"; +import { useAppState } from "../../../state/StateProvider"; +import { useUser } from "../../../state/UserProvider"; +import { PermissionLevel } from "../../../types"; +import { combineClasses, getId } from "../../../util/helpers"; +import Icon from "../../shared/Icon"; +import Grid from "../../shared/layout/Grid"; +import HoverMenu from "../../shared/menu/HoverMenu"; +import s from "../home.module.scss"; +import { NewBuild } from "./New"; + +const Builds: Component<{}> = (p) => { + const { builds } = useAppState(); + return ( + + {(id) => } + {/* */} + + ); +}; + +const Build: Component<{ id: string }> = (p) => { + const { builds } = useAppState(); + const { user } = useUser(); + const build = () => builds.get(p.id)!; + const permissionLevel = () => { + const level = build().permissions[getId(user())]; + return level ? level : PermissionLevel.None; + }; + return ( + + +
{build().name}
+ + } + content="you are a collaborator" + padding="0.5rem" + position="bottom right" + /> + +
+
+ ); +}; +export default Builds; diff --git a/frontend/src/components/home/Tree/Deployment.tsx b/frontend/src/components/home/Tree/Deployment.tsx new file mode 100644 index 000000000..0a1f56c97 --- /dev/null +++ b/frontend/src/components/home/Tree/Deployment.tsx @@ -0,0 +1,54 @@ +import { A } from "@solidjs/router"; +import { Component, Show } from "solid-js"; +import { useAppState } from "../../../state/StateProvider"; +import { useUser } from "../../../state/UserProvider"; +import { PermissionLevel } from "../../../types"; +import { combineClasses, deploymentStateClass, getId } from "../../../util/helpers"; +import Circle from "../../shared/Circle"; +import Icon from "../../shared/Icon"; +import Flex from "../../shared/layout/Flex"; +import HoverMenu from "../../shared/menu/HoverMenu"; +import s from "../home.module.scss"; + +const Deployment: Component<{ id: string }> = (p) => { + const { deployments } = useAppState(); + const { user } = useUser(); + const deployment = () => deployments.get(p.id)!; + const permissionLevel = () => { + const level = deployment().deployment.permissions![getId(user())]; + return level ? level : PermissionLevel.None; + }; + return ( + + +

{deployment().deployment.name}

+ + + } + content="you are a collaborator" + padding="0.5rem" + position="bottom center" + /> + +
{deployments.status(p.id)}
+ +
+
+
+ ); +}; + +export default Deployment; diff --git a/frontend/src/components/home/Tree/New.tsx b/frontend/src/components/home/Tree/New.tsx new file mode 100644 index 000000000..d117d3a41 --- /dev/null +++ b/frontend/src/components/home/Tree/New.tsx @@ -0,0 +1,96 @@ +import { Component, createSignal, onMount, Show } from "solid-js"; +import { client, pushNotification } from "../../.."; +import { useKeyDown, useToggle } from "../../../util/hooks"; +import Icon from "../../shared/Icon"; +import Input from "../../shared/Input"; +import Flex from "../../shared/layout/Flex"; + +export const NewDeployment: Component<{ serverID: string }> = (p) => { + const [showNew, toggleShowNew] = useToggle(); + const create = (name: string) => { + client.create_deployment({ name, server_id: p.serverID }); + }; + return ( + + + + } + > + + + ); +}; + +export const NewBuild: Component<{ serverID: string }> = (p) => { + const [showNew, toggleShowNew] = useToggle(); + const create = (name: string) => { + client.create_build({ name, server_id: p.serverID }); + }; + return ( + + + + } + > + + + ); +}; + +const New: Component<{ + create: (value: string) => void; + close: () => void; + placeholder: string; +}> = (p) => { + const [name, setName] = createSignal(""); + let inputRef: HTMLInputElement | undefined; + onMount(() => { + inputRef?.focus(); + }); + useKeyDown("Escape", p.close); + const create = () => { + if (name().length > 0) { + p.create(name()); + setName(""); + p.close(); + } else { + pushNotification("bad", "please provide a name"); + } + }; + return ( + + + + + {/* + create + */} + + + + ); +}; \ No newline at end of file diff --git a/frontend/src/components/home/Tree/Server.tsx b/frontend/src/components/home/Tree/Server.tsx new file mode 100644 index 000000000..41f52a05d --- /dev/null +++ b/frontend/src/components/home/Tree/Server.tsx @@ -0,0 +1,145 @@ +import { Component, createMemo, createSignal, For, Show } from "solid-js"; +import { useAppState } from "../../../state/StateProvider"; +import { useUser } from "../../../state/UserProvider"; +import { combineClasses, getId } from "../../../util/helpers"; +import { useLocalStorageToggle } from "../../../util/hooks"; +import Icon from "../../shared/Icon"; +import Flex from "../../shared/layout/Flex"; +import Grid from "../../shared/layout/Grid"; +import Deployment from "./Deployment"; +import s from "../home.module.scss"; +import { NewDeployment } from "./New"; +import Loading from "../../shared/loading/Loading"; +import { useNavigate } from "@solidjs/router"; +import { PermissionLevel, ServerStatus } from "../../../types"; +import { useAppDimensions } from "../../../state/DimensionProvider"; +// import StatGraphs from "../../server/StatGraphs/StatGraphs"; + +const Server: Component<{ id: string }> = (p) => { + const { servers, serverStats, deployments } = useAppState(); + const { width } = useAppDimensions(); + const { user } = useUser(); + const navigate = useNavigate(); + const [open, toggleOpen] = useLocalStorageToggle(p.id + "-homeopen"); + const server = () => servers.get(p.id); + const deploymentIDs = createMemo(() => { + return ( + deployments.loaded() && + deployments + .ids()! + .filter((id) => deployments.get(id)?.deployment.server_id === p.id) + ); + }); + const [reloading, setReloading] = createSignal(false); + const stats = () => serverStats.get(p.id); + const reloadStats = async () => { + setReloading(true); + await serverStats.load(p.id); + setReloading(false); + }; + return ( + +
+ + } + > + + + {/* */} + + + + + {/* */} + +
{ + e.stopPropagation(); + navigate(`/server/${p.id}`); + }} + > + {server()?.status.replaceAll("_", " ").toUpperCase()} +
+ + + + + {(id) => } + + + + + +
+
+ ); +}; + +export default Server; diff --git a/frontend/src/components/home/Updates/Update.tsx b/frontend/src/components/home/Updates/Update.tsx index d6e758af7..ca05d3a1f 100644 --- a/frontend/src/components/home/Updates/Update.tsx +++ b/frontend/src/components/home/Updates/Update.tsx @@ -1,8 +1,9 @@ import { Component, For, Show } from "solid-js"; import { useAppState } from "../../../state/StateProvider"; -import { Update as UpdateType } from "../../../types"; +import { Operation, Update as UpdateType } from "../../../types"; import { combineClasses, + readableDuration, readableMonitorTimestamp, } from "../../../util/helpers"; import { useToggle } from "../../../util/hooks"; @@ -26,6 +27,9 @@ const Update: Component<{ update: UpdateType }> = (p) => { } }; const operation = () => { + if (p.update.operation === Operation.BuildBuild) { + return "build"; + } return p.update.operation.replaceAll("_", " "); }; const [showLog, toggleShowLog] = useToggle(); @@ -56,7 +60,7 @@ const Update: Component<{ update: UpdateType }> = (p) => { } @@ -65,15 +69,29 @@ const Update: Component<{ update: UpdateType }> = (p) => { padding="1rem 2rem" content={ + +
+ started at: {readableMonitorTimestamp(p.update.start_ts)} +
+ +
+ duration:{" "} + {readableDuration(p.update.start_ts, p.update.end_ts!)} +
+
+
{(log, index) => { return ( - +

{log.stage}

(stage {index() + 1} of {p.update.logs.length})
+
+ {readableDuration(log.start_ts, log.end_ts)} +
command
{log.command}
diff --git a/frontend/src/components/home/home.module.scss b/frontend/src/components/home/home.module.scss index 803f67439..3fb6a1959 100644 --- a/frontend/src/components/home/home.module.scss +++ b/frontend/src/components/home/home.module.scss @@ -1,11 +1,98 @@ @use "../../style/colors.scss" as c; -.Summary { - padding: 1rem; +.Home { + width: 100%; + grid-template-columns: repeat(2, 1fr); +} + +@media only screen and (max-width: 1200px) { + .Home { + grid-template-columns: 1fr; + } } .SummaryItem { background-color: c.$lightgrey; padding: 1rem; min-width: 100px; -} \ No newline at end of file +} + +.Tabs { + // padding: 1rem; + // width: 80%; + gap: 0.5rem; +} + +.Container { + width: fit-content; + height: fit-content; + flex-wrap: wrap; + max-width: 100%; +} + +.Server { + background-color: c.$lightgrey; + height: fit-content; + cursor: pointer; + width: 100%; +} + +.ServerButton { + justify-content: space-between; + z-index: 5; + transition: background-color 500ms ease; + width: 100%; +} + +.ServerButton:hover { + background-color: rgba(c.$lightblue, 0.5); +} + +.Deployments { + background-color: c.$lightgrey; + transform-origin: top; + padding: 0.5rem; +} + +.DropdownItem { + padding: 0.5rem 1rem; + width: 100%; + justify-content: space-between; + transition: background-color 500ms ease; +} + +.DropdownItem:hover { + background-color: rgba(c.$lightblue, 0.5); +} + +.UpdatesContainer { + max-height: 50vh; +} + +.Summary { + padding: 1rem; +} + +.Updates { + padding: 1rem; +} + +// @media only screen and (max-width: 1200px) { +// .Summary { +// width: 80%; +// } + +// .Updates { +// width: 80% +// } +// } + +// @media only screen and (max-width: 500px) { +// .Summary { +// width: 95%; +// } + +// .Updates { +// width: 95% +// } +// } \ No newline at end of file diff --git a/frontend/src/style/index.scss b/frontend/src/style/index.scss index c850129e8..bab0a77ee 100644 --- a/frontend/src/style/index.scss +++ b/frontend/src/style/index.scss @@ -76,6 +76,7 @@ a { background-color: transparent; transition: background-color 500ms ease-in-out; text-decoration: none; + box-sizing: border-box; } a:focus { diff --git a/frontend/src/util/helpers.ts b/frontend/src/util/helpers.ts index 562d08a0e..58cf17896 100644 --- a/frontend/src/util/helpers.ts +++ b/frontend/src/util/helpers.ts @@ -44,6 +44,14 @@ export function readableMonitorTimestamp(rfc3339_ts: string) { } ${pm ? "PM" : "AM"}`; } +export function readableDuration(start_ts: string, end_ts: string) { + const start = new Date(start_ts); + const end = new Date(end_ts); + const durr = end.getTime() - start.getTime(); + const seconds = (durr / 1000).toFixed(1); + return `${seconds} seconds` +} + export function validatePercentage(perc: string) { // validates that a string represents a percentage const percNum = Number(perc);