forked from github-starred/komodo
add usernames cache to state
This commit is contained in:
@@ -18,7 +18,7 @@ const AddServer: Component<{}> = () => {
|
||||
target="add server"
|
||||
targetClass="green shadow"
|
||||
targetStyle={{ width: "100%" }}
|
||||
content={<Content close={toggleShow} />}
|
||||
content={() => <Content close={toggleShow} />}
|
||||
position="center"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ import UpdateMenu from "../../update/UpdateMenu";
|
||||
import s from "./update.module.scss";
|
||||
|
||||
const Update: Component<{ update: UpdateType }> = (p) => {
|
||||
const { deployments, servers, builds } = useAppState();
|
||||
const { deployments, servers, builds, usernames } = useAppState();
|
||||
const name = () => {
|
||||
if (p.update.target.type === "Deployment" && deployments.loaded()) {
|
||||
return deployments.get(p.update.target.id!)?.deployment.name || "deleted";
|
||||
@@ -53,7 +53,7 @@ const Update: Component<{ update: UpdateType }> = (p) => {
|
||||
</div>
|
||||
<Flex gap="0.5rem">
|
||||
<Icon type="user" />
|
||||
<div>{p.update.operator}</div>
|
||||
<div>{usernames.get(p.update.operator)}</div>
|
||||
</Flex>
|
||||
</Grid>
|
||||
<UpdateMenu update={p.update} />
|
||||
|
||||
@@ -16,7 +16,7 @@ const StatGraphs: Component<{ id: string }> = (p) => {
|
||||
toggleShow={toggleShow}
|
||||
target={<Icon type="timeline-line-chart" width="0.85rem" />}
|
||||
targetClass="blue"
|
||||
content={<Graphs id={p.id} />}
|
||||
content={() => <Graphs id={p.id} />}
|
||||
title={`${name()} stats`}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@ import s from "./menu.module.scss";
|
||||
const CenterMenu: Component<{
|
||||
show: Accessor<boolean>;
|
||||
toggleShow: () => void;
|
||||
content: JSXElement;
|
||||
content: () => JSXElement;
|
||||
target: JSXElement;
|
||||
targetStyle?: JSX.CSSProperties;
|
||||
targetClass?: string;
|
||||
@@ -58,7 +58,7 @@ const CenterMenu: Component<{
|
||||
|
||||
const Child: Component<{
|
||||
title?: string;
|
||||
content: JSXElement;
|
||||
content: () => JSXElement;
|
||||
show: Accessor<boolean>;
|
||||
toggleShow: () => void;
|
||||
padding?: string | number;
|
||||
@@ -95,7 +95,7 @@ const Child: Component<{
|
||||
</button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{p.content}
|
||||
{p.content()}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
@@ -1,68 +1,38 @@
|
||||
import { Component, For, Show } from "solid-js";
|
||||
import { Component, Show } from "solid-js";
|
||||
import { useAppState } from "../../state/StateProvider";
|
||||
import { Update as UpdateType } from "../../types";
|
||||
import {
|
||||
combineClasses,
|
||||
readableDuration,
|
||||
readableMonitorTimestamp,
|
||||
} from "../../util/helpers";
|
||||
import { useToggle } from "../../util/hooks";
|
||||
import Icon from "../shared/Icon";
|
||||
import Flex from "../shared/layout/Flex";
|
||||
import Grid from "../shared/layout/Grid";
|
||||
import CenterMenu from "../shared/menu/CenterMenu";
|
||||
import s from "./update.module.scss";
|
||||
import UpdateMenu from "./UpdateMenu";
|
||||
|
||||
const Update: Component<{ update: UpdateType; showName: boolean }> = (p) => {
|
||||
const { deployments, servers, builds } = useAppState();
|
||||
const name = () => {
|
||||
if (p.update.target.type === "Deployment" && deployments.loaded()) {
|
||||
return deployments.get(p.update.target.id!)?.deployment.name || "deleted";
|
||||
} else if (p.update.target.type === "Server" && servers.loaded()) {
|
||||
return servers.get(p.update.target.id)?.server.name || "deleted";
|
||||
} else if (p.update.target.type === "Build" && builds.loaded()) {
|
||||
return builds.get(p.update.target.id)?.name || "deleted";
|
||||
} else {
|
||||
return "monitor";
|
||||
}
|
||||
};
|
||||
const Update: Component<{ update: UpdateType }> = (p) => {
|
||||
const { usernames } = useAppState();
|
||||
const operation = () => {
|
||||
return p.update.operation.replaceAll("_", " ")
|
||||
};
|
||||
const [showLog, toggleShowLog] = useToggle();
|
||||
return (
|
||||
<Grid
|
||||
gap="0.25rem"
|
||||
class={combineClasses(s.Update, !p.showName && s.NoName, "shadow")}
|
||||
>
|
||||
<Show when={p.showName}>
|
||||
<h2 style={{ "place-self": "center" }}>{name()}</h2>
|
||||
</Show>
|
||||
<Grid
|
||||
gap="0.25rem"
|
||||
<Grid gap="0.25rem" class={combineClasses(s.Update, "shadow")}>
|
||||
<div
|
||||
style={{
|
||||
"grid-template-columns": "1fr 1fr",
|
||||
"grid-template-rows": "1fr 1fr",
|
||||
color: !p.update.success ? "rgb(182, 47, 52)" : "inherit",
|
||||
}}
|
||||
placeItems="center start"
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
color: !p.update.success ? "rgb(182, 47, 52)" : "inherit",
|
||||
}}
|
||||
>
|
||||
{operation()}
|
||||
</div>
|
||||
<div style={{ "place-self": "center end" }}>
|
||||
{readableMonitorTimestamp(p.update.start_ts)}
|
||||
</div>
|
||||
<Flex alignItems="center">
|
||||
<Icon type="user" />
|
||||
<div>{p.update.operator}</div>
|
||||
</Flex>
|
||||
{operation()}
|
||||
</div>
|
||||
<div style={{ "place-self": "start end" }}>{readableMonitorTimestamp(p.update.start_ts)}</div>
|
||||
<Flex gap="0.5rem" alignItems="center">
|
||||
<Icon type="user" />
|
||||
<div>{usernames.get(p.update.operator)}</div>
|
||||
</Flex>
|
||||
<Flex style={{ "place-self": "center end" }} alignItems="center">
|
||||
<UpdateMenu update={p.update} />
|
||||
</Grid>
|
||||
</Flex>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, For, Show } from "solid-js";
|
||||
import { Component, createResource, For, Show } from "solid-js";
|
||||
import { client } from "../..";
|
||||
import { useAppState } from "../../state/StateProvider";
|
||||
import { Update as UpdateType } from "../../types";
|
||||
import { combineClasses, readableDuration, readableMonitorTimestamp } from "../../util/helpers";
|
||||
@@ -36,63 +37,70 @@ const UpdateMenu: Component<{ update: UpdateType }> = (p) => {
|
||||
targetClass="blue"
|
||||
padding="1rem 2rem"
|
||||
content={
|
||||
<Grid class={s.LogContainer} gap="1rem">
|
||||
<Grid gap="0.5rem" class="card light shadow">
|
||||
<div>started at: {readableMonitorTimestamp(p.update.start_ts)}</div>
|
||||
<Show when={p.update.end_ts}>
|
||||
<div>
|
||||
duration:{" "}
|
||||
{readableDuration(p.update.start_ts, p.update.end_ts!)}
|
||||
</div>
|
||||
</Show>
|
||||
</Grid>
|
||||
<For each={p.update.logs}>
|
||||
{(log, index) => {
|
||||
return (
|
||||
<Grid gap="0.5rem" class="card light shadow">
|
||||
<Flex alignItems="center" class="wrap">
|
||||
<h1>{log.stage}</h1>
|
||||
<div style={{ opacity: 0.7 }}>
|
||||
(stage {index() + 1} of {p.update.logs.length})
|
||||
</div>
|
||||
<div style={{ opacity: 0.7 }}>
|
||||
{readableDuration(log.start_ts, log.end_ts)}
|
||||
</div>
|
||||
</Flex>
|
||||
<Show when={log.command}>
|
||||
<div>command</div>
|
||||
<pre class={combineClasses(s.Log)}>{log.command}</pre>
|
||||
</Show>
|
||||
<Show when={log.stdout}>
|
||||
<div>stdout</div>
|
||||
<pre
|
||||
class={combineClasses(s.Log)}
|
||||
// style={{
|
||||
// "max-height": log.stderr ? "30vh" : "60vh",
|
||||
// }}
|
||||
>
|
||||
{log.stdout}
|
||||
</pre>
|
||||
</Show>
|
||||
<Show when={log.stderr}>
|
||||
<div>stderr</div>
|
||||
<pre
|
||||
class={combineClasses(s.Log)}
|
||||
// style={{
|
||||
// "max-height": log.stdout ? "30vh" : "60vh",
|
||||
// }}
|
||||
>
|
||||
{log.stderr}
|
||||
</pre>
|
||||
</Show>
|
||||
</Grid>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</Grid>
|
||||
() => <UpdateMenuContent update={p.update} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateMenu;
|
||||
export default UpdateMenu;
|
||||
|
||||
const UpdateMenuContent: Component<{ update: UpdateType }> = (p) => {
|
||||
const { usernames } = useAppState();
|
||||
return (
|
||||
<Grid class={s.LogContainer} gap="1rem">
|
||||
<Grid gap="0.5rem" class="card light shadow">
|
||||
<div>operator: {usernames.get(p.update.operator)}</div>
|
||||
<div>started at: {readableMonitorTimestamp(p.update.start_ts)}</div>
|
||||
<Show when={p.update.end_ts}>
|
||||
<div>
|
||||
duration: {readableDuration(p.update.start_ts, p.update.end_ts!)}
|
||||
</div>
|
||||
</Show>
|
||||
</Grid>
|
||||
<For each={p.update.logs}>
|
||||
{(log, index) => {
|
||||
return (
|
||||
<Grid gap="0.5rem" class="card light shadow">
|
||||
<Flex alignItems="center" class="wrap">
|
||||
<h1>{log.stage}</h1>
|
||||
<div style={{ opacity: 0.7 }}>
|
||||
(stage {index() + 1} of {p.update.logs.length})
|
||||
</div>
|
||||
<div style={{ opacity: 0.7 }}>
|
||||
{readableDuration(log.start_ts, log.end_ts)}
|
||||
</div>
|
||||
</Flex>
|
||||
<Show when={log.command}>
|
||||
<div>command</div>
|
||||
<pre class={combineClasses(s.Log)}>{log.command}</pre>
|
||||
</Show>
|
||||
<Show when={log.stdout}>
|
||||
<div>stdout</div>
|
||||
<pre
|
||||
class={combineClasses(s.Log)}
|
||||
// style={{
|
||||
// "max-height": log.stderr ? "30vh" : "60vh",
|
||||
// }}
|
||||
>
|
||||
{log.stdout}
|
||||
</pre>
|
||||
</Show>
|
||||
<Show when={log.stderr}>
|
||||
<div>stderr</div>
|
||||
<pre
|
||||
class={combineClasses(s.Log)}
|
||||
// style={{
|
||||
// "max-height": log.stdout ? "30vh" : "60vh",
|
||||
// }}
|
||||
>
|
||||
{log.stderr}
|
||||
</pre>
|
||||
</Show>
|
||||
</Grid>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@@ -14,7 +14,9 @@
|
||||
.Update {
|
||||
background-color: c.$lightgrey;
|
||||
padding: 0.75rem;
|
||||
height: 90px;
|
||||
height: 70px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
transform-origin: top;
|
||||
animation-name: Enter;
|
||||
animation-duration: 750ms;
|
||||
@@ -33,7 +35,7 @@
|
||||
}
|
||||
|
||||
.Update.NoName {
|
||||
height: 70px;
|
||||
// height: 70px;
|
||||
animation-name: EnterNoName;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,14 @@ import {
|
||||
useServers,
|
||||
useServerStats,
|
||||
useUpdates,
|
||||
useUsernames,
|
||||
} from "./hooks";
|
||||
import connectToWs from "./ws";
|
||||
import { useUser } from "./UserProvider";
|
||||
import { PermissionLevel } from "../types";
|
||||
|
||||
export type State = {
|
||||
usernames: ReturnType<typeof useUsernames>
|
||||
servers: ReturnType<typeof useServers>;
|
||||
getPermissionOnServer: (id: string) => PermissionLevel;
|
||||
serverStats: ReturnType<typeof useServerStats>;
|
||||
@@ -37,7 +39,9 @@ export const AppStateProvider: ParentComponent = (p) => {
|
||||
const servers = useServers();
|
||||
const builds = useBuilds();
|
||||
const deployments = useDeployments();
|
||||
const usernames = useUsernames();
|
||||
const state: State = {
|
||||
usernames,
|
||||
servers,
|
||||
getPermissionOnServer: (id: string) => {
|
||||
const server = servers.get(id)!;
|
||||
|
||||
@@ -45,6 +45,31 @@ export function useServerStats() {
|
||||
};
|
||||
}
|
||||
|
||||
export function useUsernames() {
|
||||
const [usernames, set] = createSignal<Record<string, string | undefined>>(
|
||||
{}
|
||||
);
|
||||
const load = async (userID: string) => {
|
||||
const username = await client.get_username(userID);
|
||||
set((s) => ({ ...s, [userID]: username }));
|
||||
};
|
||||
const loading: Record<string, boolean> = {};
|
||||
return {
|
||||
get: (userID: string) => {
|
||||
const username = usernames()[userID];
|
||||
if (
|
||||
username === undefined &&
|
||||
!loading[userID]
|
||||
) {
|
||||
loading[userID] = true;
|
||||
load(userID);
|
||||
}
|
||||
return username;
|
||||
},
|
||||
load,
|
||||
};
|
||||
}
|
||||
|
||||
const buildIdPath = ["_id", "$oid"];
|
||||
|
||||
export function useBuilds() {
|
||||
|
||||
@@ -42,9 +42,8 @@ function connectToWs(state: State) {
|
||||
return {
|
||||
subscribe: (operations: Operation[], callback: (update: Update) => void) => {
|
||||
const listener = ({ data }: { data: string }) => {
|
||||
if (data === "PONG") {
|
||||
return;
|
||||
}
|
||||
if (data === "PONG") return;
|
||||
if (data = "LOGGED_IN") return;
|
||||
const update = JSON.parse(data) as Update;
|
||||
if (operations.length === 0 || operations.includes(update.operation)) {
|
||||
callback(update);
|
||||
|
||||
Reference in New Issue
Block a user