Merge branch 'responsive'

This commit is contained in:
beckerinj
2023-01-17 23:27:18 -05:00
18 changed files with 211 additions and 212 deletions

View File

@@ -1,6 +1,5 @@
import { useNavigate, useParams } from "@solidjs/router";
import { Component, createEffect, onCleanup, Show } from "solid-js";
import { MAX_PAGE_WIDTH } from "../..";
import { useAppDimensions } from "../../state/DimensionProvider";
import { useAppState } from "../../state/StateProvider";
import { useUser } from "../../state/UserProvider";
@@ -14,16 +13,16 @@ import Header from "./Header";
import BuildTabs from "./tabs/Tabs";
import Updates from "./Updates";
const Build2: Component<{}> = (p) => {
const Build: Component<{}> = (p) => {
const { builds, ws } = useAppState();
const navigate = useNavigate();
const params = useParams();
const build = () => builds.get(params.id)!;
const { isSemiMobile } = useAppDimensions();
const { user } = useUser();
const userCanUpdate = () =>
user().admin ||
build().permissions![getId(user())] === PermissionLevel.Update;
// const { user } = useUser();
// const userCanUpdate = () =>
// user().admin ||
// build().permissions![getId(user())] === PermissionLevel.Update;
let unsub = () => {};
createEffect(() => {
unsub();
@@ -39,58 +38,22 @@ const Build2: Component<{}> = (p) => {
<ActionStateProvider>
<Grid
style={{
width: "100vw",
"max-width": `${MAX_PAGE_WIDTH}px`,
width: "100%",
"box-sizing": "border-box",
}}
>
<Grid style={{ width: "100%" }} gridTemplateColumns="1fr 1fr">
<Grid
style={{ width: "100%" }}
gridTemplateColumns={isSemiMobile() ? "1fr" : "1fr 1fr"}
>
<Grid style={{ "flex-grow": 1, "grid-auto-rows": "auto 1fr" }}>
<Header />
<Actions />
</Grid>
<Updates />
</Grid>
<BuildTabs />
</Grid>
</ActionStateProvider>
</Show>
);
};
const Build: Component<{}> = (p) => {
const { builds, ws } = useAppState();
const navigate = useNavigate();
const params = useParams();
const build = () => builds.get(params.id)!;
const { isSemiMobile } = useAppDimensions();
const { user } = useUser();
const userCanUpdate = () =>
user().admin ||
build().permissions![getId(user())] === PermissionLevel.Update;
let unsub = () => {};
createEffect(() => {
unsub();
unsub = ws.subscribe([Operation.DeleteBuild], (update) => {
if (update.target.id === params.id) {
navigate("/");
}
});
});
onCleanup(() => unsub);
return (
<Show when={build()} fallback={<NotFound type="build" />}>
<ActionStateProvider>
<Grid class={combineClasses("content")}>
{/* left / actions */}
<Grid class="left-content">
<Header />
<Actions />
<Show when={!isSemiMobile() && userCanUpdate()}>
<Show when={!isSemiMobile()}>
<Updates />
</Show>
</Grid>
{/* right / tabs */}
<BuildTabs />
</Grid>
</ActionStateProvider>
@@ -98,4 +61,4 @@ const Build: Component<{}> = (p) => {
);
};
export default Build2;
export default Build;

View File

@@ -18,7 +18,7 @@ const Header: Component<{}> = (p) => {
const params = useParams();
const build = () => builds.get(params.id)!;
const { user } = useUser();
const { isMobile } = useAppDimensions();
const { isSemiMobile } = useAppDimensions();
const [showUpdates, toggleShowUpdates] =
useLocalStorageToggle("show-updates");
const userCanUpdate = () =>
@@ -32,10 +32,10 @@ const Header: Component<{}> = (p) => {
alignItems="center"
style={{
position: "relative",
cursor: isMobile() && userCanUpdate() ? "pointer" : undefined,
cursor: isSemiMobile() ? "pointer" : undefined,
}}
onClick={() => {
if (isMobile() && userCanUpdate()) toggleShowUpdates();
if (isSemiMobile()) toggleShowUpdates();
}}
>
<Grid gap="0.1rem">
@@ -54,7 +54,7 @@ const Header: Component<{}> = (p) => {
<Icon type="trash" />
</ConfirmButton>
</Show>
<Show when={isMobile() && userCanUpdate()}>
<Show when={isSemiMobile()}>
<Flex gap="0.5rem" alignItems="center" class="show-updates-indicator">
updates{" "}
<Icon
@@ -64,7 +64,7 @@ const Header: Component<{}> = (p) => {
</Flex>
</Show>
</Flex>
<Show when={isMobile() && userCanUpdate() && showUpdates()}>
<Show when={isSemiMobile() && showUpdates()}>
<Updates />
</Show>
</>

View File

@@ -17,6 +17,7 @@ import Updates from "./Updates";
const Deployment2: Component<{}> = (p) => {
const { servers, deployments } = useAppState();
const { isSemiMobile, isMobile } = useAppDimensions();
const params = useParams();
const deployment = () => deployments.get(params.id);
const server = () =>
@@ -29,17 +30,21 @@ const Deployment2: Component<{}> = (p) => {
<ActionStateProvider>
<Grid
style={{
width: "100vw",
"max-width": `${MAX_PAGE_WIDTH}px`,
width: "100%",
"box-sizing": "border-box",
}}
>
<Grid style={{ width: "100%" }} gridTemplateColumns="1fr 1fr">
<Grid
style={{ width: "100%" }}
gridTemplateColumns={isSemiMobile() ? "1fr" : "1fr 1fr"}
>
<Grid style={{ "flex-grow": 1, "grid-auto-rows": "auto 1fr" }}>
<Header />
<Actions />
</Grid>
<Updates />
<Show when={!isMobile()}>
<Updates />
</Show>
</Grid>
<DeploymentTabs />
</Grid>

View File

@@ -24,7 +24,7 @@ const Header: Component<{}> = (p) => {
deployment()!.state === DockerContainerState.NotDeployed
? undefined
: deployment().container?.status?.toLowerCase();
const { isMobile } = useAppDimensions();
const { isSemiMobile } = useAppDimensions();
const [showUpdates, toggleShowUpdates] =
useLocalStorageToggle("show-updates");
const userCanUpdate = () =>
@@ -38,11 +38,11 @@ const Header: Component<{}> = (p) => {
class={combineClasses("card shadow")}
style={{
position: "relative",
cursor: isMobile() && userCanUpdate() ? "pointer" : undefined,
height: "fit-content"
cursor: isSemiMobile() && userCanUpdate() ? "pointer" : undefined,
height: "fit-content",
}}
onClick={() => {
if (isMobile() && userCanUpdate()) toggleShowUpdates();
if (isSemiMobile() && userCanUpdate()) toggleShowUpdates();
}}
>
<Flex alignItems="center" justifyContent="space-between">
@@ -52,7 +52,7 @@ const Header: Component<{}> = (p) => {
target={
<ConfirmButton
onConfirm={() => {
client.delete_deployment(params.id)
client.delete_deployment(params.id);
}}
class="red"
>
@@ -73,7 +73,7 @@ const Header: Component<{}> = (p) => {
<div style={{ opacity: 0.7 }}>{status()}</div>
</Show>
</Flex>
<Show when={isMobile() && userCanUpdate()}>
<Show when={isSemiMobile()}>
<Flex gap="0.5rem" alignItems="center" class="show-updates-indicator">
updates{" "}
<Icon
@@ -83,7 +83,7 @@ const Header: Component<{}> = (p) => {
</Flex>
</Show>
</Grid>
<Show when={isMobile() && userCanUpdate() && showUpdates()}>
<Show when={isSemiMobile() && showUpdates()}>
<Updates />
</Show>
</>

View File

@@ -19,24 +19,20 @@ const Home2: Component<{}> = (p) => {
// const { width } = useAppDimensions();
const { servers } = useAppState();
return (
<Grid placeItems="center start" style={{ "width": "100vw", "max-width": `${MAX_PAGE_WIDTH}px`, "box-sizing": "border-box" }}>
{/* <Summary /> */}
<SimpleTabs
containerStyle={{ width: "100%" }}
localStorageKey="home-groups-servers-tab-v1"
tabs={[
{
title: "groups",
element: () => <Groups />,
},
{
title: "servers",
element: () => <Servers serverIDs={servers.ids()!} showAdd />,
},
]}
/>
{/* <Updates /> */}
</Grid>
<SimpleTabs
containerStyle={{ width: "100%" }}
localStorageKey="home-groups-servers-tab-v1"
tabs={[
{
title: "groups",
element: () => <Groups />,
},
{
title: "servers",
element: () => <Servers serverIDs={servers.ids()!} showAdd />,
},
]}
/>
);
};

View File

@@ -69,6 +69,7 @@ const Group: Component<{ id: string }> = (p) => {
<h1 style={{ "font-size": "1.25rem" }}>{group()?.name}</h1>
</Flex>
<Flex alignItems="center">
<h2>{serverIDs()!.length} server{serverIDs()!.length > 1 ? "s" : ""}</h2>
<Show when={open()}>
<button
class="blue"

View File

@@ -7,7 +7,7 @@ import {
} from "solid-js";
import { useAppState } from "../../../state/StateProvider";
import { useUser } from "../../../state/UserProvider";
import { combineClasses, getId } from "../../../util/helpers";
import { combineClasses, getId, readableStorageAmount } from "../../../util/helpers";
import { useLocalStorageToggle } from "../../../util/hooks";
import Icon from "../../shared/Icon";
import Flex from "../../shared/layout/Flex";
@@ -25,7 +25,7 @@ import SimpleTabs from "../../shared/tabs/SimpleTabs";
const Server: Component<{ id: string }> = (p) => {
const { servers, serverStats, deployments, builds } = useAppState();
const { width } = useAppDimensions();
const { isSemiMobile } = useAppDimensions();
const { user } = useUser();
const [open, toggleOpen] = useLocalStorageToggle(p.id + "-homeopen");
const server = () => servers.get(p.id);
@@ -43,13 +43,6 @@ const Server: Component<{ id: string }> = (p) => {
.ids()!
.filter((id) => builds.get(id)?.server_id === p.id)) as string[];
});
const [reloading, setReloading] = createSignal(false);
const stats = () => serverStats.get(p.id);
const reloadStats = async () => {
setReloading(true);
await serverStats.load(p.id);
setReloading(false);
};
return (
<Show when={server()}>
<div class={combineClasses(s.Server, "shadow")}>
@@ -62,54 +55,7 @@ const Server: Component<{ id: string }> = (p) => {
<h1 style={{ "font-size": "1.25rem" }}>{server()?.server.name}</h1>
</Flex>
<Flex alignItems="center">
<Show when={width() > 500 && server()?.status === ServerStatus.Ok}>
<Show when={stats()} fallback={<Loading type="three-dot" />}>
<div>
<div style={{ opacity: 0.7 }}>cpu:</div>{" "}
{stats()!.cpu_perc.toFixed(1)}%
</div>
<div>
<div style={{ opacity: 0.7 }}>mem:</div>{" "}
{(
(100 * stats()!.mem_used_gb) /
stats()!.mem_total_gb
).toFixed(1)}
%
</div>
<div>
<div style={{ opacity: 0.7 }}>disk:</div>{" "}
{(
(100 * stats()!.disk.used_gb) /
stats()!.disk.total_gb
).toFixed(1)}
%
</div>
<Flex gap=".5rem" alignItems="center">
<Show
when={!reloading()}
fallback={
<button class="blue" style={{ height: "fit-content" }}>
<Loading type="spinner" scale={0.2} />
</button>
}
>
<button
class="blue"
style={{ height: "fit-content" }}
onClick={(e) => {
e.stopPropagation();
reloadStats();
}}
>
<Icon type="refresh" width="0.85rem" />
</button>
</Show>
<A href={`/server/${p.id}/stats`} class="blue" onClick={e => e.stopPropagation()}>
<Icon type="timeline-line-chart" />
</A>
</Flex>
</Show>
</Show>
<ServerStats id={p.id} />
<A
href={`/server/${p.id}`}
class={
@@ -146,6 +92,7 @@ const Server: Component<{ id: string }> = (p) => {
s.Deployments,
open() ? s.Enter : s.Exit
)}
gridTemplateColumns={isSemiMobile() ? "1fr" : "1fr 1fr"}
>
<For each={deploymentIDs()}>
{(id) => <Deployment id={id} />}
@@ -171,6 +118,7 @@ const Server: Component<{ id: string }> = (p) => {
s.Deployments,
open() ? s.Enter : s.Exit
)}
gridTemplateColumns={isSemiMobile() ? "1fr" : "1fr 1fr"}
>
<For each={buildIDs()}>{(id) => <Build id={id} />}</For>
<Show
@@ -194,3 +142,110 @@ const Server: Component<{ id: string }> = (p) => {
};
export default Server;
const ServerStats: Component<{ id: string }> = (p) => {
const { servers, serverStats } = useAppState();
const { isMobile, isSemiMobile } = useAppDimensions();
const server = () => servers.get(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 (
<Show when={!isMobile() && server()?.status === ServerStatus.Ok}>
<Show when={stats()} fallback={<Loading type="three-dot" />}>
<Grid
// gap="0.25rem"
placeItems="center"
gridTemplateColumns={
isSemiMobile() ? "100px 100px 100px" : "90px 160px 160px"
}
>
<Flex
style={{
width: "100%",
"box-sizing": "border-box",
}}
gap="0.5rem"
alignItems="center"
justifyContent="center"
>
<div style={{ opacity: 0.7 }}>cpu:</div>
<h2>{stats()!.cpu_perc.toFixed(1)}%</h2>
</Flex>
<Flex
style={{
width: "100%",
"box-sizing": "border-box",
}}
gap="0.5rem"
alignItems="center"
justifyContent="center"
>
<div style={{ opacity: 0.7 }}>mem:</div>
<h2>
{((100 * stats()!.mem_used_gb) / stats()!.mem_total_gb).toFixed(
1
)}
%
</h2>
<Show when={!isSemiMobile()}>
<div>{stats()!.mem_total_gb.toFixed()} GiB</div>
</Show>
</Flex>
<Flex
style={{
width: "100%",
"box-sizing": "border-box",
}}
gap="0.5rem"
alignItems="center"
justifyContent="center"
>
<div style={{ opacity: 0.7 }}>disk:</div>
<h2>
{((100 * stats()!.disk.used_gb) / stats()!.disk.total_gb).toFixed(
1
)}
%
</h2>
<Show when={!isSemiMobile()}>
<div>{readableStorageAmount(stats()!.disk.total_gb)}</div>
</Show>
</Flex>
</Grid>
<Flex gap=".5rem" alignItems="center">
<Show
when={!reloading()}
fallback={
<button class="blue" style={{ height: "fit-content" }}>
<Loading type="spinner" scale={0.2} />
</button>
}
>
<button
class="blue"
style={{ height: "fit-content" }}
onClick={(e) => {
e.stopPropagation();
reloadStats();
}}
>
<Icon type="refresh" width="0.85rem" />
</button>
</Show>
<A
href={`/server/${p.id}/stats`}
class="blue"
onClick={(e) => e.stopPropagation()}
>
<Icon type="timeline-line-chart" />
</A>
</Flex>
</Show>
</Show>
);
}

View File

@@ -1,16 +1,5 @@
@use "../../style/colors.scss" as c;
.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;

View File

@@ -1,4 +1,4 @@
import { Component, Show } from "solid-js";
import { Component, createResource, Show } from "solid-js";
import { useAppState } from "../../state/StateProvider";
import { useUser } from "../../state/UserProvider";
import { combineClasses, getId, serverStatusClass } from "../../util/helpers";
@@ -12,6 +12,7 @@ import Updates from "./Updates";
import { PermissionLevel, Server } from "../../types";
import { A, useParams } from "@solidjs/router";
import { client } from "../..";
import Loading from "../shared/loading/Loading";
const Header: Component<{}> = (p) => {
const { servers } = useAppState();
@@ -19,12 +20,15 @@ const Header: Component<{}> = (p) => {
const server = () => servers.get(params.id)!;
const status = () => server().status.replaceAll("_", " ").toUpperCase();
const { user } = useUser();
const { isMobile } = useAppDimensions();
const { isMobile, isSemiMobile } = useAppDimensions();
const [showUpdates, toggleShowUpdates] =
useLocalStorageToggle("show-updates");
const userCanUpdate = () =>
user().admin ||
server().server.permissions![getId(user())] === PermissionLevel.Update;
const [version] = createResource(async () => {
return await client.get_server_version(params.id).catch();
});
return (
<>
<Flex
@@ -33,10 +37,10 @@ const Header: Component<{}> = (p) => {
alignItems="center"
style={{
position: "relative",
cursor: isMobile() && userCanUpdate() ? "pointer" : undefined,
cursor: isSemiMobile() ? "pointer" : undefined,
}}
onClick={() => {
if (isMobile() && userCanUpdate()) toggleShowUpdates();
if (isSemiMobile()) toggleShowUpdates();
}}
>
<Grid gap="0.1rem">
@@ -50,6 +54,11 @@ const Header: Component<{}> = (p) => {
</Flex>
</Grid>
<Flex alignItems="center">
<Show when={!isMobile()}>
<Show when={version()} fallback={<Loading type="three-dot" />}>
<div style={{ opacity: 0.7 }}>periphery v{version()}</div>
</Show>
</Show>
<div class={serverStatusClass(server().status)}>{status()}</div>
<A
href={`/server/${params.id}/stats`}
@@ -69,7 +78,7 @@ const Header: Component<{}> = (p) => {
</ConfirmButton>
</Show>
</Flex>
<Show when={isMobile() && userCanUpdate()}>
<Show when={isSemiMobile()}>
<Flex gap="0.5rem" alignItems="center" class="show-updates-indicator">
updates{" "}
<Icon
@@ -79,7 +88,7 @@ const Header: Component<{}> = (p) => {
</Flex>
</Show>
</Flex>
<Show when={isMobile() && userCanUpdate() && showUpdates()}>
<Show when={isSemiMobile() && showUpdates()}>
<Updates />
</Show>
</>

View File

@@ -1,13 +1,8 @@
import { useParams } from "@solidjs/router";
import { Component, Show } from "solid-js";
import { MAX_PAGE_WIDTH } from "../..";
import { useAppDimensions } from "../../state/DimensionProvider";
import { useAppState } from "../../state/StateProvider";
import { useUser } from "../../state/UserProvider";
import { PermissionLevel } from "../../types";
import { combineClasses, getId } from "../../util/helpers";
import NotFound from "../NotFound";
import Flex from "../shared/layout/Flex";
import Grid from "../shared/layout/Grid";
import Actions from "./Actions";
import { ActionStateProvider } from "./ActionStateProvider";
@@ -15,11 +10,11 @@ import Header from "./Header";
import ServerTabs from "./tabs/Tabs";
import Updates from "./Updates";
const Server2: Component<{}> = (p) => {
const Server: Component<{}> = (p) => {
const { servers } = useAppState();
const params = useParams();
const server = () => servers.get(params.id)!;
const { user } = useUser();
const { isSemiMobile } = useAppDimensions();
// const userCanUpdate = () =>
// user().admin ||
// server()!.server.permissions![getId(user())] === PermissionLevel.Update;
@@ -28,47 +23,22 @@ const Server2: Component<{}> = (p) => {
<ActionStateProvider>
<Grid
style={{
width: "100vw",
"max-width": `${MAX_PAGE_WIDTH}px`,
width: "100%",
"box-sizing": "border-box",
}}
>
<Grid style={{ width: "100%" }} gridTemplateColumns="1fr 1fr">
<Grid
style={{ width: "100%" }}
gridTemplateColumns={isSemiMobile() ? "1fr" : "1fr 1fr"}
>
<Grid>
<Header />
<Actions />
</Grid>
<Updates />
</Grid>
<ServerTabs />
</Grid>
</ActionStateProvider>
</Show>
);
};
const Server: Component<{}> = (p) => {
const { servers } = useAppState();
const params = useParams();
const server = () => servers.get(params.id)!;
const { isSemiMobile } = useAppDimensions();
const { user } = useUser();
const userCanUpdate = () =>
user().admin ||
server()!.server.permissions![getId(user())] === PermissionLevel.Update;
return (
<Show when={server()} fallback={<NotFound type="server" />}>
<ActionStateProvider>
<Grid class={combineClasses("content")}>
{/* left / actions */}
<Grid class="left-content">
<Header />
<Actions />
<Show when={!isSemiMobile() && userCanUpdate()}>
<Show when={!isSemiMobile()}>
<Updates />
</Show>
</Grid>
{/* right / tabs */}
<ServerTabs />
</Grid>
</ActionStateProvider>
@@ -76,4 +46,4 @@ const Server: Component<{}> = (p) => {
);
};
export default Server2;
export default Server;

View File

@@ -78,7 +78,6 @@ const CurrentStats2: Component<{}> = (p) => {
style={{
width: "100%",
"box-sizing": "border-box",
"max-width": `${MAX_PAGE_WIDTH}px`,
}}
gridTemplateColumns="auto 1fr auto"
>

View File

@@ -39,8 +39,7 @@ const StatsComp: Component<{}> = () => {
return (
<Grid
style={{
width: "100vw",
"max-width": `${MAX_PAGE_WIDTH}px`,
width: "100%",
"box-sizing": "border-box",
}}
>

View File

@@ -27,7 +27,7 @@ const mobileStyle: JSX.CSSProperties = {
};
export const Search: Component<{}> = (p) => {
const { isMobile } = useAppDimensions();
const { isSemiMobile } = useAppDimensions();
const { search, open, input } = useSearchState();
let inputRef: HTMLInputElement | undefined;
useWindowKeyDown((e) => {
@@ -44,12 +44,12 @@ export const Search: Component<{}> = (p) => {
menuClass={s.SearchMenu}
menuStyle={{
gap: "0.5rem",
...(isMobile() ? mobileStyle : {}),
...(isSemiMobile() ? mobileStyle : {}),
}}
backgroundColor={isMobile() ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.4)"}
backgroundColor={isSemiMobile() ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.4)"}
target={
<Show
when={!isMobile()}
when={!isSemiMobile()}
fallback={
<button class="grey" onClick={() => open.set(true)}>
<Icon type="search" width="1.15rem" />

View File

@@ -29,7 +29,7 @@ const Topbar: Component = () => {
<Grid
class={combineClasses(s.Topbar, "shadow")}
placeItems="center"
style={{ height: inPx(TOPBAR_HEIGHT) }}
style={{ height: inPx(TOPBAR_HEIGHT), width: "100%" }}
>
<LeftSide />
<SearchProvider>

View File

@@ -1,9 +1,6 @@
@use "../../style/colors.scss" as c;
.Topbar {
grid-area: topbar;
width: 100vw;
max-width: 1200px;
box-sizing: border-box;
background-color: c.$grey;
z-index: 90;

View File

@@ -3,14 +3,12 @@
.app {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto auto;
gap: 1rem;
padding: 1rem;
width: 100vw;
max-width: calc(1200px + 2rem);
box-sizing: border-box;
grid-template-areas:
"topbar"
"content";
place-items: start center;
place-items: center;
}
@media only screen and (max-width: 1200px) {

View File

@@ -10,6 +10,10 @@ body {
background-color: c.$darkgrey;
color: c.$app-color;
display: grid;
grid-template-columns: 1fr;
place-items: center;
}
code {
@@ -96,6 +100,12 @@ input {
-webkit-appearance: none;
}
@media only screen and (max-width: 700px) {
input {
width: 200px;
}
}
input:focus {
outline: solid 1px rgba(c.$lightgrey, 0.7);
}

View File

@@ -211,4 +211,12 @@ export function convert_timelength_to_ms(timelength: Timelength) {
} else if (timelength === Timelength.OneMinute) {
return 60000;
}
}
export function readableStorageAmount(gb: number) {
if (gb > 512) {
return `${(gb / 1024).toFixed(1)} TB`
} else {
return `${gb.toFixed()} GiB`
}
}