feat: initial prototype

This commit is contained in:
2026-02-13 15:52:13 +07:00
parent 134351b326
commit e8dbefde43
140 changed files with 12390 additions and 1369 deletions

View File

@@ -0,0 +1,112 @@
"use client";
import type {
CustomResourceSummary,
DeploymentInfo,
K8sConfigMapSummary,
K8sServiceSummary,
K8sStatus,
StatefulSetInfo,
} from "@minikura/api";
import { LABEL_PREFIX } from "@minikura/api";
import { useCallback, useEffect, useState } from "react";
import { api } from "@/lib/api";
export function useK8sResources() {
const [statefulSets, setStatefulSets] = useState<StatefulSetInfo[]>([]);
const [deployments, setDeployments] = useState<DeploymentInfo[]>([]);
const [services, setServices] = useState<K8sServiceSummary[]>([]);
const [configMaps, setConfigMaps] = useState<K8sConfigMapSummary[]>([]);
const [minecraftServers, setMinecraftServers] = useState<CustomResourceSummary[]>([]);
const [reverseProxyServers, setReverseProxyServers] = useState<CustomResourceSummary[]>([]);
const [status, setStatus] = useState<K8sStatus>({ initialized: false });
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchData = useCallback(async () => {
try {
const [
statusRes,
podsRes,
deploymentsRes,
statefulSetsRes,
servicesRes,
configMapsRes,
minecraftServersRes,
reverseProxyServersRes,
] = await Promise.allSettled([
api.api.k8s.status.get(),
api.api.k8s.pods.get(),
api.api.k8s.deployments.get(),
api.api.k8s.statefulsets.get(),
api.api.k8s.services.get(),
api.api.k8s.configmaps.get(),
api.api.k8s["minecraft-servers"].get(),
api.api.k8s["reverse-proxy-servers"].get(),
]);
if (statefulSetsRes.status === "fulfilled" && statefulSetsRes.value.data) {
setStatefulSets(statefulSetsRes.value.data as StatefulSetInfo[]);
} else if (statefulSetsRes.status === "rejected") {
console.error("Failed to fetch statefulsets:", statefulSetsRes.reason);
}
if (deploymentsRes.status === "fulfilled" && deploymentsRes.value.data) {
setDeployments(deploymentsRes.value.data as DeploymentInfo[]);
} else if (deploymentsRes.status === "rejected") {
console.error("Failed to fetch deployments:", deploymentsRes.reason);
}
if (servicesRes.status === "fulfilled" && servicesRes.value.data) {
setServices(servicesRes.value.data as K8sServiceSummary[]);
} else if (servicesRes.status === "rejected") {
console.error("Failed to fetch services:", servicesRes.reason);
}
if (configMapsRes.status === "fulfilled" && configMapsRes.value.data) {
setConfigMaps(configMapsRes.value.data as K8sConfigMapSummary[]);
} else if (configMapsRes.status === "rejected") {
console.error("Failed to fetch configmaps:", configMapsRes.reason);
}
if (minecraftServersRes.status === "fulfilled" && minecraftServersRes.value.data) {
setMinecraftServers(minecraftServersRes.value.data as CustomResourceSummary[]);
} else if (minecraftServersRes.status === "rejected") {
console.error("Failed to fetch minecraft servers:", minecraftServersRes.reason);
}
if (reverseProxyServersRes.status === "fulfilled" && reverseProxyServersRes.value.data) {
setReverseProxyServers(reverseProxyServersRes.value.data as CustomResourceSummary[]);
} else if (reverseProxyServersRes.status === "rejected") {
console.error("Failed to fetch reverse proxy servers:", reverseProxyServersRes.reason);
}
} catch (err: unknown) {
const errorMessage =
err instanceof Error ? err.message : "Failed to fetch Kubernetes resources";
setError(errorMessage);
} finally {
setLoading(false);
}
}, []);
// biome-ignore lint/correctness/useExhaustiveDependencies: fetchData intentionally omitted to avoid infinite loop
useEffect(() => {
fetchData();
const interval = setInterval(fetchData, 30000);
return () => clearInterval(interval);
}, []);
return {
statefulSets,
deployments,
services,
configMaps,
minecraftServers,
reverseProxyServers,
status,
loading,
error,
refresh: fetchData,
labelPrefix: LABEL_PREFIX,
};
}

View File

@@ -0,0 +1,61 @@
"use client";
import type { NormalServer, ReverseProxyServer } from "@minikura/api";
import { useCallback, useEffect, useState } from "react";
import { getReverseProxyApi } from "@/lib/api-helpers";
import { api } from "@/lib/api";
export function useServerList() {
const [normalServers, setNormalServers] = useState<NormalServer[]>([]);
const [reverseProxies, setReverseProxies] = useState<ReverseProxyServer[]>([]);
const [loading, setLoading] = useState(true);
const fetchServers = useCallback(async () => {
try {
const [normalRes, proxyRes] = await Promise.all([
api.api.servers.get(),
getReverseProxyApi().get(),
]);
if (normalRes.data) {
setNormalServers(normalRes.data as unknown as NormalServer[]);
}
if (proxyRes.data) {
setReverseProxies(proxyRes.data as unknown as ReverseProxyServer[]);
}
} catch (error) {
console.error("Failed to fetch servers:", error);
} finally {
setLoading(false);
}
}, []);
const deleteServer = useCallback(
async (id: string, type: "normal" | "proxy") => {
try {
if (type === "normal") {
await api.api.servers({ id }).delete();
} else {
await getReverseProxyApi()({ id }).delete();
}
await fetchServers();
} catch (error) {
console.error("Failed to delete server:", error);
throw error;
}
},
[fetchServers]
);
useEffect(() => {
fetchServers();
}, [fetchServers]);
return {
normalServers,
reverseProxies,
loading,
refresh: fetchServers,
deleteServer,
};
}

View File

@@ -0,0 +1,95 @@
"use client";
import type {
ConnectionInfo,
DeploymentInfo,
PodInfo,
StatefulSetInfo,
} from "@minikura/api";
import { labelKeys } from "@minikura/api";
import { useCallback, useState } from "react";
import { api } from "@/lib/api";
export function useServerLogs(serverId: string) {
const [pods, setPods] = useState<PodInfo[]>([]);
const [statefulSetInfo, setStatefulSetInfo] = useState<StatefulSetInfo | null>(null);
const [deploymentInfo, setDeploymentInfo] = useState<DeploymentInfo | null>(null);
const [connectionInfo, setConnectionInfo] = useState<ConnectionInfo | null>(null);
const [loading, setLoading] = useState(true);
const fetchPods = useCallback(async () => {
try {
const response = await api.api.k8s.pods.get();
if (response.data) {
setPods(response.data as PodInfo[]);
}
} catch (error) {
console.error("Failed to fetch pods:", error);
} finally {
setLoading(false);
}
}, []);
const fetchStatefulSetInfo = useCallback(async () => {
try {
const response = await api.api.k8s.statefulsets.get();
if (response.data) {
const statefulSets = response.data as StatefulSetInfo[];
const serverStatefulSet = statefulSets.find(
(s) => s.labels?.[labelKeys.serverId] === serverId
);
if (serverStatefulSet) {
setStatefulSetInfo(serverStatefulSet);
}
}
} catch (error) {
console.error("Failed to fetch StatefulSet info:", error);
}
}, [serverId]);
const fetchDeploymentInfo = useCallback(async () => {
try {
const response = await api.api.k8s.deployments.get();
if (response.data) {
const deployments = response.data as DeploymentInfo[];
const serverDeployment = deployments.find(
(d) => d.labels?.[labelKeys.serverId] === serverId
);
if (serverDeployment) {
setDeploymentInfo(serverDeployment);
}
}
} catch (error) {
console.error("Failed to fetch Deployment info:", error);
}
}, [serverId]);
const fetchConnectionInfo = useCallback(async () => {
try {
const response = await api.api.servers({ id: serverId })["connection-info"].get();
if (response.data) {
setConnectionInfo(response.data as ConnectionInfo);
}
} catch (error) {
console.error("Failed to fetch connection info:", error);
}
}, [serverId]);
const refreshAll = useCallback(async () => {
await Promise.all([
fetchPods(),
fetchStatefulSetInfo(),
fetchDeploymentInfo(),
fetchConnectionInfo(),
]);
}, [fetchPods, fetchStatefulSetInfo, fetchDeploymentInfo, fetchConnectionInfo]);
return {
pods,
statefulSetInfo,
deploymentInfo,
connectionInfo,
loading,
refresh: refreshAll,
};
}