handle stuff when server disabled / unreachable

This commit is contained in:
mbecker20
2023-01-28 09:03:00 +00:00
parent e4336f19f3
commit 237a1d802d
16 changed files with 160 additions and 102 deletions

View File

@@ -10,20 +10,21 @@ import { useActionStates } from "./ActionStateProvider";
import { client } from "../..";
import { combineClasses, getId } from "../../util/helpers";
import { useParams } from "@solidjs/router";
import { PermissionLevel } from "../../types";
import { PermissionLevel, ServerStatus } from "../../types";
const Actions: Component<{}> = (p) => {
const { user } = useUser();
const params = useParams() as { id: string };
const { builds } = useAppState();
const { builds, servers } = useAppState();
const build = () => builds.get(params.id)!;
const server = () => build() && servers.get(build()!.server_id);
const actions = useActionStates();
const userCanExecute = () =>
user().admin ||
build().permissions![getId(user())] === PermissionLevel.Execute ||
build().permissions![getId(user())] === PermissionLevel.Update;
return (
<Show when={userCanExecute()}>
<Show when={userCanExecute() && server()?.status === ServerStatus.Ok}>
<Grid class={combineClasses("card shadow")} gridTemplateRows="auto 1fr">
<h1>actions</h1>
<Grid style={{ height: "fit-content" }}>

View File

@@ -10,7 +10,7 @@ import { createStore, SetStoreFunction } from "solid-js/store";
import { client } from "../../..";
import { useAppState } from "../../../state/StateProvider";
import { useUser } from "../../../state/UserProvider";
import { Build, Operation, PermissionLevel } from "../../../types";
import { Build, Operation, PermissionLevel, ServerWithStatus } from "../../../types";
import { getId } from "../../../util/helpers";
type ConfigBuild = Build & {
@@ -22,6 +22,7 @@ type ConfigBuild = Build & {
type State = {
build: ConfigBuild;
setBuild: SetStoreFunction<ConfigBuild>;
server: () => ServerWithStatus | undefined
reset: () => void;
save: () => void;
userCanUpdate: () => boolean;
@@ -30,7 +31,7 @@ type State = {
const context = createContext<State>();
export const ConfigProvider: ParentComponent<{}> = (p) => {
const { ws, builds } = useAppState();
const { ws, builds, servers } = useAppState();
const params = useParams();
const { user } = useUser();
const [build, set] = createStore({
@@ -44,6 +45,7 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
set(...args);
set("updated", true);
};
const server = () => servers.get(builds.get(params.id)!.server_id);
const load = () => {
// console.log("load build");
@@ -105,6 +107,7 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
const state = {
build,
setBuild,
server,
reset: load,
save,
userCanUpdate,

View File

@@ -1,6 +1,7 @@
import { Component, createEffect, createSignal, Show } from "solid-js";
import { client } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { ServerStatus } from "../../../../types";
import { combineClasses } from "../../../../util/helpers";
import Input from "../../../shared/Input";
import Flex from "../../../shared/layout/Flex";
@@ -9,10 +10,14 @@ import Selector from "../../../shared/menu/Selector";
import { useConfig } from "../Provider";
const Docker: Component<{}> = (p) => {
const { build, setBuild, userCanUpdate } = useConfig();
const { build, setBuild, server, userCanUpdate } = useConfig();
const [dockerAccounts, setDockerAccounts] = createSignal<string[]>();
createEffect(() => {
client.get_server_docker_accounts(build.server_id).then(setDockerAccounts);
if (server()?.status === ServerStatus.Ok) {
client
.get_server_docker_accounts(build.server_id)
.then(setDockerAccounts);
}
});
return (
<Grid class={combineClasses("config-item shadow")}>

View File

@@ -1,4 +1,4 @@
import { Component, createEffect, createSignal, Show } from "solid-js";
import { Component, createEffect, createSignal } from "solid-js";
import Grid from "../../../shared/layout/Grid";
import { useConfig } from "../Provider";
import Flex from "../../../shared/layout/Flex";
@@ -6,12 +6,15 @@ import Input from "../../../shared/Input";
import Selector from "../../../shared/menu/Selector";
import { combineClasses } from "../../../../util/helpers";
import { client } from "../../../..";
import { ServerStatus } from "../../../../types";
const Git: Component<{}> = (p) => {
const { build, setBuild, userCanUpdate } = useConfig();
const { build, setBuild, server, userCanUpdate } = useConfig();
const [githubAccounts, setGithubAccounts] = createSignal<string[]>();
createEffect(() => {
client.get_server_github_accounts(build.server_id).then(setGithubAccounts)
if (server()?.status === ServerStatus.Ok) {
client.get_server_github_accounts(build.server_id).then(setGithubAccounts);
}
});
return (
<Grid class={combineClasses("config-item shadow")}>

View File

@@ -1,5 +1,5 @@
import { Component, Match, Show, Switch } from "solid-js";
import { client, pushNotification } from "../..";
import { client } from "../..";
import { useAppState } from "../../state/StateProvider";
import { useUser } from "../../state/UserProvider";
import ConfirmButton from "../shared/ConfirmButton";
@@ -9,24 +9,32 @@ import Grid from "../shared/layout/Grid";
import Loading from "../shared/loading/Loading";
import HoverMenu from "../shared/menu/HoverMenu";
import { useActionStates } from "./ActionStateProvider";
import { combineClasses, getId } from "../../util/helpers";
import { combineClasses } from "../../util/helpers";
import { A, useParams } from "@solidjs/router";
import { DockerContainerState, PermissionLevel } from "../../types";
import {
DockerContainerState,
PermissionLevel,
ServerStatus,
} from "../../types";
const Actions: Component<{}> = (p) => {
const { deployments, builds, getPermissionOnDeployment } = useAppState();
const { deployments, builds, servers, getPermissionOnDeployment } =
useAppState();
const params = useParams();
const { user, user_id } = useUser();
const deployment = () => deployments.get(params.id)!;
const server = () =>
deployment() && servers.get(deployment()!.deployment.server_id);
const show = () => {
const permissions = getPermissionOnDeployment(params.id);
return (
server()?.status === ServerStatus.Ok &&
deployment() &&
(user().admin ||
permissions === PermissionLevel.Execute ||
permissions === PermissionLevel.Update)
);
};
const deployment = () => deployments.get(params.id)!;
const showBuild = () => {
const build = deployment().deployment.build_id
? builds.get(deployment().deployment.build_id!)

View File

@@ -17,6 +17,7 @@ import {
DockerContainerState,
Log as LogType,
Operation,
ServerStatus,
} from "../../../types";
import { client } from "../../..";
import SimpleTabs from "../../shared/tabs/SimpleTabs";
@@ -26,18 +27,22 @@ import { useUser } from "../../../state/UserProvider";
const DeploymentTabs: Component<{}> = () => {
const { user } = useUser();
const { deployments, ws } = useAppState();
const { deployments, ws, servers } = useAppState();
const params = useParams();
const deployment = () => deployments.get(params.id);
const server = () => deployment() && servers.get(deployment()!.deployment.server_id)
const [logTail, setLogTail] = createSignal(50);
const [log, setLog] = createSignal<LogType>();
const status = () =>
deployment()!.state === DockerContainerState.NotDeployed
? "not deployed"
: deployment()!.container?.state;
const log_available = () =>
server()?.status === ServerStatus.Ok &&
deployment()?.state !== DockerContainerState.NotDeployed;
const loadLog = async () => {
console.log("load log");
if (deployment()?.state !== DockerContainerState.NotDeployed) {
if (log_available()) {
console.log("load log");
const log = await client.get_deployment_container_log(
params.id,
logTail()
@@ -78,7 +83,7 @@ const DeploymentTabs: Component<{}> = () => {
title: "config",
element: () => <Config />,
},
status() !== "not deployed" && [
log_available() && [
{
title: "log",
element: () => (
@@ -90,7 +95,7 @@ const DeploymentTabs: Component<{}> = () => {
/>
),
},
status() !== "not deployed" && {
{
title: "error log",
titleElement: () => (
<Flex gap="0.5rem" alignItems="center">

View File

@@ -12,7 +12,7 @@ import { createStore, SetStoreFunction } from "solid-js/store";
import { client, pushNotification } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { useUser } from "../../../../state/UserProvider";
import { Deployment, Operation, PermissionLevel } from "../../../../types";
import { Deployment, Operation, PermissionLevel, ServerStatus, ServerWithStatus } from "../../../../types";
import { getId } from "../../../../util/helpers";
type ConfigDeployment = Deployment & {
@@ -25,6 +25,7 @@ type State = {
editing: Accessor<boolean>;
deployment: ConfigDeployment;
setDeployment: SetStoreFunction<ConfigDeployment>;
server: () => ServerWithStatus | undefined;
reset: () => void;
save: () => void;
networks: Accessor<any[]>;
@@ -34,7 +35,7 @@ type State = {
const context = createContext<State>();
export const ConfigProvider: ParentComponent<{}> = (p) => {
const { ws, deployments } = useAppState();
const { ws, deployments, servers } = useAppState();
const params = useParams();
const { user } = useUser();
const [editing] = createSignal(false);
@@ -87,11 +88,13 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
createEffect(load);
const [networks, setNetworks] = createSignal<any[]>([]);
const server = () => servers.get(deployments.get(params.id)!.deployment.server_id);
createEffect(() => {
console.log("load networks");
client
.get_docker_networks(deployments.get(params.id)!.deployment.server_id)
.then(setNetworks);
if (server()?.status === ServerStatus.Ok) {
client
.get_docker_networks(deployments.get(params.id)!.deployment.server_id)
.then(setNetworks);
}
});
const save = () => {
@@ -141,6 +144,7 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
editing,
deployment,
setDeployment,
server,
reset: load,
save,
networks,

View File

@@ -1,18 +1,20 @@
import { Component, createEffect, createSignal, Show } from "solid-js";
import { client } from "../../../../..";
import { useAppState } from "../../../../../state/StateProvider";
import { ServerStatus } from "../../../../../types";
import { combineClasses } from "../../../../../util/helpers";
import Flex from "../../../../shared/layout/Flex";
import Selector from "../../../../shared/menu/Selector";
import { useConfig } from "../Provider";
const DockerAccount: Component<{}> = (p) => {
const { deployment, setDeployment, userCanUpdate } = useConfig();
const { deployment, setDeployment, server, userCanUpdate } = useConfig();
const [dockerAccounts, setDockerAccounts] = createSignal<string[]>();
createEffect(() => {
client
.get_server_docker_accounts(deployment.server_id)
.then(setDockerAccounts);
if (server()?.status === ServerStatus.Ok) {
client
.get_server_docker_accounts(deployment.server_id)
.then(setDockerAccounts);
}
});
return (
<Flex

View File

@@ -1,5 +1,6 @@
import { Component, createEffect, createSignal } from "solid-js";
import { client } from "../../../../..";
import { ServerStatus } from "../../../../../types";
import { combineClasses } from "../../../../../util/helpers";
import Input from "../../../../shared/Input";
import Flex from "../../../../shared/layout/Flex";
@@ -8,10 +9,14 @@ import Selector from "../../../../shared/menu/Selector";
import { useConfig } from "../Provider";
const Git: Component<{}> = (p) => {
const { deployment, setDeployment, userCanUpdate } = useConfig();
const { deployment, server, setDeployment, userCanUpdate } = useConfig();
const [githubAccounts, setGithubAccounts] = createSignal<string[]>();
createEffect(() => {
client.get_server_github_accounts(deployment.server_id).then(setGithubAccounts);
if (server()?.status === ServerStatus.Ok) {
client
.get_server_github_accounts(deployment.server_id)
.then(setGithubAccounts);
}
});
return (
<Grid class={combineClasses("config-item shadow")}>

View File

@@ -25,25 +25,27 @@ const Actions: Component<{}> = (p) => {
<Show
when={server() && server().status === ServerStatus.Ok && userCanExecute()}
>
<Grid class={combineClasses("card shadow")}>
<Grid class={combineClasses("card shadow")} gridTemplateRows="auto 1fr">
<h1>actions</h1>
<Flex class={combineClasses("action shadow")}>
prune images <PruneImages />
</Flex>
<Flex class={combineClasses("action shadow")}>
prune containers <PruneContainers />
</Flex>
<Flex class={combineClasses("action shadow")}>
prune networks{" "}
<ConfirmButton
class="green"
onConfirm={() => {
client.prune_docker_networks(params.id);
}}
>
<Icon type="cut" />
</ConfirmButton>
</Flex>
<Grid style={{ height: "fit-content" }}>
<Flex class={combineClasses("action shadow")}>
prune images <PruneImages />
</Flex>
<Flex class={combineClasses("action shadow")}>
prune containers <PruneContainers />
</Flex>
<Flex class={combineClasses("action shadow")}>
prune networks{" "}
<ConfirmButton
class="green"
onConfirm={() => {
client.prune_docker_networks(params.id);
}}
>
<Icon type="cut" />
</ConfirmButton>
</Flex>
</Grid>
</Grid>
</Show>
);

View File

@@ -9,10 +9,11 @@ import Grid from "../shared/layout/Grid";
import { useAppDimensions } from "../../state/DimensionProvider";
import { useLocalStorageToggle } from "../../util/hooks";
import Updates from "./Updates";
import { PermissionLevel, Server } from "../../types";
import { PermissionLevel, ServerStatus } from "../../types";
import { A, useParams } from "@solidjs/router";
import { client } from "../..";
import Loading from "../shared/loading/Loading";
import HoverMenu from "../shared/menu/HoverMenu";
const Header: Component<{}> = (p) => {
const { servers } = useAppState();
@@ -26,25 +27,58 @@ const Header: Component<{}> = (p) => {
const userCanUpdate = () =>
user().admin ||
server().server.permissions![getId(user())] === PermissionLevel.Update;
const [version] = createResource(async () => {
return await client.get_server_version(params.id).catch();
});
const [version] = createResource(
() => server() && server().status === ServerStatus.Ok,
async (do_it?: boolean) => {
if (!do_it) return;
return await client.get_server_version(params.id).catch();
}
);
return (
<>
<Flex
<Grid
gap="0.5rem"
class={combineClasses("card shadow")}
justifyContent="space-between"
alignItems="center"
style={{
position: "relative",
cursor: isSemiMobile() ? "pointer" : undefined,
height: "fit-content",
}}
onClick={() => {
if (isSemiMobile()) toggleShowUpdates();
}}
>
<Grid gap="0.1rem">
<Flex alignItems="center" justifyContent="space-between">
<h1>{server().server.name}</h1>
<Show when={userCanUpdate()}>
<Flex alignItems="center">
<div class={serverStatusClass(server().status)}>{status()}</div>
<A
href={`/server/${params.id}/stats`}
class="blue"
onClick={(e) => e.stopPropagation()}
>
<Icon type="timeline-line-chart" />
</A>
<HoverMenu
target={
<ConfirmButton
onConfirm={() => {
client.delete_server(params.id);
}}
class="red"
>
<Icon type="trash" />
</ConfirmButton>
}
content="delete server"
position="bottom center"
padding="0.5rem"
/>
</Flex>
</Show>
</Flex>
<Flex alignItems="center" justifyContent="space-between">
<Flex gap="0.2rem" alignItems="center" style={{ opacity: 0.8 }}>
<div>server</div>
<Show when={server().server.region}>
@@ -52,42 +86,13 @@ const Header: Component<{}> = (p) => {
{server().server.region}
</Show>
</Flex>
</Grid>
<Flex alignItems="center">
<Show when={!isMobile()}>
<Show when={version()} fallback={<Loading type="three-dot" />}>
<Show when={version()}>
<div style={{ opacity: 0.7 }}>periphery v{version()}</div>
</Show>
</Show>
<div class={serverStatusClass(server().status)}>{status()}</div>
<A
href={`/server/${params.id}/stats`}
class="blue"
onClick={(e) => e.stopPropagation()}
>
<Icon type="timeline-line-chart" />
</A>
<Show when={userCanUpdate()}>
<ConfirmButton
onConfirm={() => {
client.delete_server(params.id);
}}
class="red"
>
<Icon type="trash" />
</ConfirmButton>
</Show>
</Flex>
<Show when={isSemiMobile()}>
<Flex gap="0.5rem" alignItems="center" class="show-updates-indicator">
updates{" "}
<Icon
type={showUpdates() ? "chevron-up" : "chevron-down"}
width="0.9rem"
/>
</Flex>
</Show>
</Flex>
</Grid>
<Show when={isSemiMobile() && showUpdates()}>
<Updates />
</Show>

View File

@@ -32,7 +32,7 @@ const Server: Component<{}> = (p) => {
style={{ width: "100%" }}
gridTemplateColumns={isSemiMobile() ? "1fr" : "1fr 1fr"}
>
<Grid>
<Grid style={{ "flex-grow": 1, "grid-auto-rows": "auto 1fr" }}>
<Header />
<Actions />
</Grid>

View File

@@ -2,6 +2,7 @@ import { useParams } from "@solidjs/router";
import { Component, Show } from "solid-js";
import { useAppState } from "../../../state/StateProvider";
import { useUser } from "../../../state/UserProvider";
import { ServerStatus } from "../../../types";
import SimpleTabs from "../../shared/tabs/SimpleTabs";
import { Tab } from "../../shared/tabs/Tabs";
import Config from "./config/Config";
@@ -26,7 +27,7 @@ const ServerTabs: Component<{}> = (p) => {
title: "config",
element: () => <Config />,
},
{
server()?.status === ServerStatus.Ok && {
title: "info",
element: () => <Info />
},

View File

@@ -12,7 +12,7 @@ import { createStore, SetStoreFunction } from "solid-js/store";
import { client } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { useUser } from "../../../../state/UserProvider";
import { Server, Operation, PermissionLevel } from "../../../../types";
import { Server, Operation, PermissionLevel, ServerStatus } from "../../../../types";
import { getId } from "../../../../util/helpers";
type ConfigServer = Server & { loaded: boolean; updated: boolean };
@@ -59,7 +59,9 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
const [networks, setNetworks] = createSignal<any[]>([]);
const loadNetworks = () => {
// console.log("load networks");
client.get_docker_networks(params.id).then(setNetworks);
if (servers.get(params.id)?.status === ServerStatus.Ok) {
client.get_docker_networks(params.id).then(setNetworks);
}
};
createEffect(loadNetworks);

View File

@@ -102,8 +102,8 @@ export const AppStateProvider: ParentComponent = (p) => {
return PermissionLevel.None;
}
},
serverStats: useServerStats(),
serverInfo: useServerInfo(),
serverStats: useServerStats(servers),
serverInfo: useServerInfo(servers),
groups,
getPermissionOnGroup: (id: string) => {
const group = groups.get(id)!;

View File

@@ -47,13 +47,19 @@ export function useServers() {
);
}
export function useServerStats() {
export function useServerStats(servers: ReturnType<typeof useServers>) {
const [stats, set] = createSignal<Record<string, SystemStats | undefined>>(
{}
);
const load = async (serverID: string) => {
const stats = await client.get_server_stats(serverID);
set((s) => ({ ...s, [serverID]: stats }));
if (servers.get(serverID)?.status === ServerStatus.Ok) {
try {
const stats = await client.get_server_stats(serverID);
set((s) => ({ ...s, [serverID]: stats }));
} catch (error) {
console.log("error getting server stats");
}
}
};
const loading: Record<string, boolean> = {};
setTimeout(() => Object.keys(stats()).forEach(load), 30000);
@@ -74,13 +80,19 @@ export function useServerStats() {
};
}
export function useServerInfo() {
export function useServerInfo(servers: ReturnType<typeof useServers>) {
const [info, set] = createSignal<
Record<string, SystemInformation | undefined>
>({});
const load = async (serverID: string) => {
const info = await client.get_server_system_info(serverID);
set((s) => ({ ...s, [serverID]: info }));
if (servers.get(serverID)?.status === ServerStatus.Ok) {
try {
const info = await client.get_server_system_info(serverID);
set((s) => ({ ...s, [serverID]: info }));
} catch (error) {
console.log("error getting server info", error);
}
}
};
const loading: Record<string, boolean> = {};
return {