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
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();
|