You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

122 lines
3.1 KiB
TypeScript

import { swel } from "https://git.idylls.net/idylls/swel/raw/tag/2022.07.27/mod.ts";
import { makeClient, sig, ClientResponse, isHttpError, isValidationError, assert } from "https://git.idylls.net/idylls/yaypi/raw/tag/2022.07.27/mod.ts";
import { array, object, string, TypeOf, union, nativeEnum, ZodTypeAny } from "https://deno.land/x/zod@v3.17.10/mod.ts";
export const GetConfigurationResponseV1 = object({
hosts: array(string()),
services: array(object({
name: string(),
publicUrl: string().optional(),
})),
});
export type GetConfigurationResponseV1 = TypeOf<typeof GetConfigurationResponseV1>;
export enum StatusV1 {
Down = "down",
Up = "up",
}
export const JsonResult = <T extends ZodTypeAny>(ok: T) => union([
object({ ok, }),
object({ error: string() }),
]);
export const StatusResponseV1 = JsonResult(nativeEnum(StatusV1));
export type StatusResponseV1 = TypeOf<typeof StatusResponseV1>;
export const api = {
v1: {
configuration: {
get: sig(undefined, GetConfigurationResponseV1),
},
host: {
getStatus: sig(string(), StatusResponseV1),
},
service: {
getStatus: sig(string(), StatusResponseV1),
},
},
};
const client = (() => {
const l = window.location;
const url = `${l.protocol}//${l.host}/api`;
return makeClient(api, url);
})();
const ServiceInfo = (
name: string,
getStatus: () => Promise<ClientResponse<StatusResponseV1>>,
publicUrl?: string,
) => {
const nameEl = swel("div", { className: "name" }, name);
const status = swel("div", { className: "status" }, "loading");
const go = publicUrl ? swel("a", {
className: "go",
href: publicUrl,
target: "_blank",
on: { mouseenter: (e) => e.preventDefault() },
}, "Go") : null;
const button = swel("button", {
className: "status-button",
tabIndex: 0,
}, [nameEl, status]);
const refresh = async () => {
button.removeEventListener("click", refresh);
button.disabled = true;
status.textContent = "loading";
button.className = "status-button";
const st = await getStatus();
if (isHttpError(st)) {
status.textContent = "error";
button.classList.add("error");
} else if (isValidationError(st)) {
status.textContent = st.error.toString();
button.classList.add("error");
} else if ("error" in st) {
status.textContent = st.error;
button.classList.add("error");
} else {
status.textContent = st.ok;
button.classList.add(st.ok);
}
button.addEventListener("click", refresh);
button.disabled = false;
};
refresh();
return swel("div", { className: "status-container", }, [button, go]);
}
const main = async () => {
const resp = await client.v1.configuration.get().then(assert);
const hostsSection = swel("section", [
swel("h1", "Hosts"),
swel(
"div",
{ className: "hosts" },
resp.hosts.map(h => ServiceInfo(
h,
() => client.v1.host.getStatus(h),
))
),
]);
const servicesSection = swel("section", [
swel("h1", "Services"),
swel("div", { className: "services", }, resp.services.map(si => ServiceInfo(
si.name,
() => client.v1.service.getStatus(si.name),
si.publicUrl,
))),
]);
document.body.replaceChildren(hostsSection, servicesSection);
};
main();