turboci-admin/src/components/pages/admin/services/(partials)/service.tsx
2026-03-10 16:53:06 +00:00

163 lines
5.5 KiB
TypeScript

import { AppContext } from "@/src/pages/_app";
import {
NormalizedServerObject,
ParsedDeploymentServiceConfig,
} from "@/src/types";
import ArrowedLink from "@/twui/components/layout/ArrowedLink";
import Button from "@/twui/components/layout/Button";
import H2 from "@/twui/components/layout/H2";
import Row from "@/twui/components/layout/Row";
import Stack from "@/twui/components/layout/Stack";
import { useContext, useEffect, useRef, useState } from "react";
import ServiceClusterServer from "../service/(partials)/cluster-server";
import { twMerge } from "tailwind-merge";
import Select from "@/twui/components/form/Select";
import useStatus from "@/twui/components/hooks/useStatus";
import Loading from "@/twui/components/elements/Loading";
import _ from "lodash";
type Props = {
service: ParsedDeploymentServiceConfig;
};
export default function DeploymentService({ service }: Props) {
const { pageProps, ws } = useContext(AppContext);
const { deployment } = pageProps;
const { ready, setReady } = useStatus();
const children_services = deployment?.services.filter(
(srv) => srv.parent_service_name == service.service_name,
);
const all_clusters = [service, ...(children_services || [])];
const [targetCluster, setTargetCluster] = useState<
ParsedDeploymentServiceConfig | undefined
>(all_clusters?.[0]);
const targetClusterIndex =
all_clusters.findIndex(
(cl) => cl.service_name == targetCluster?.service_name,
) + 1;
const cluster_servers = targetCluster?.servers;
const [targetServer, setTargetServer] = useState<
NormalizedServerObject | undefined
>(cluster_servers?.[0]);
const portRef = useRef<number>(undefined);
function killPort(port: number) {
ws.sendData({
event: "client:kill-port",
server: targetServer,
service: _.omit(service, ["servers"]),
port,
});
}
useEffect(() => {
setTargetServer(targetCluster?.servers?.[0]);
}, [targetCluster]);
useEffect(() => {
setReady(false);
if (targetServer && portRef.current) {
killPort(portRef.current);
}
setTimeout(() => {
setReady(true);
}, 2000);
}, [targetServer]);
return (
<Stack className="grid-cell">
<Stack className="grid-cell-content">
<Row className="w-full justify-between">
<H2>{service.service_name}</H2>
<ArrowedLink
link={{
url: `/admin/services/${service.service_name}`,
title: `View`,
}}
/>
</Row>
<code>{service.service_name} service</code>
</Stack>
<hr />
<Stack className="gap-0">
<Row className="p-4 grid md:grid-cols-2">
<Select
options={all_clusters.map((cl, index) => ({
value: cl.service_name,
title: `Cluster #${index + 1}`,
}))}
changeHandler={(v) => {
setTargetCluster(
all_clusters.find((cl) => cl.service_name == v),
);
}}
/>
{cluster_servers ? (
<Select
options={cluster_servers.map((srv) => ({
value: srv.private_ip!,
title: srv.private_ip,
}))}
changeHandler={(v) => {
setTargetServer(
cluster_servers.find(
(srv) => srv.private_ip == v,
),
);
}}
/>
) : undefined}
{/* {cluster_servers?.map((server, index) => {
const is_active =
server?.private_ip == targetServer?.private_ip;
return (
<Button
title={`Cluster ${index + 1}`}
key={index}
color="gray"
variant={is_active ? undefined : "outlined"}
size="small"
onClick={() => {
setTargetServer(server);
}}
>
<span
className={twMerge(
is_active ? "font-semibold" : "",
)}
>
{server.private_ip}
</span>
</Button>
);
})} */}
</Row>
{ready ? (
targetServer ? (
<ServiceClusterServer
server={targetServer}
service={service}
portRef={portRef}
/>
) : undefined
) : undefined}
</Stack>
</Stack>
);
}