ai_api / src /app /(dashboard) /dashboard /settings /components /RedisLauncherPanel.tsx
Yogesh
initial deploy
cd8bd0a
Raw
History Blame Contribute Delete
5.42 kB
"use client";
import { useState } from "react";
import { useTranslations } from "next-intl";
import { Button } from "@/shared/components";
type LaunchState = "idle" | "checking" | "launching" | "ready" | "error";
type Status = {
exists?: boolean;
running?: boolean;
reachable?: boolean;
message?: string;
detail?: string;
};
async function apiCall(endpoint: string, options: RequestInit = {}) {
const res = await fetch(`/api/local/redis${endpoint}`, {
method: options.method || "GET",
headers: { "Content-Type": "application/json" },
body: options.body,
cache: "no-store",
});
if (!res.ok) {
let detail = `HTTP ${res.status}`;
try {
const json = await res.json();
if (json?.error) detail = json.error;
} catch {
// ignore
}
throw new Error(detail);
}
return res.json();
}
/**
* Compact 1-click Redis control. Sits inside the resilience settings tab and
* shells out to the same logic exposed via the `omniroute redis` CLI command.
* The actual container management is delegated to the server-side endpoint
* at /api/local/redis/* so the browser never executes podman/docker directly.
*/
export default function RedisLauncherPanel() {
const t = useTranslations("settings");
const [state, setState] = useState<LaunchState>("idle");
const [status, setStatus] = useState<Status | null>(null);
const [error, setError] = useState<string | null>(null);
async function refresh() {
setState("checking");
setError(null);
try {
const data = await apiCall("/status");
setStatus(data);
setState(data.running ? "ready" : "idle");
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to query status");
setState("error");
}
}
async function launch() {
setState("launching");
setError(null);
try {
const data = await apiCall("/start", { method: "POST" });
setStatus(data);
setState("ready");
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to launch Redis");
setState("error");
}
}
async function stop() {
setState("launching");
setError(null);
try {
await apiCall("/stop", { method: "POST" });
setStatus({ exists: false, running: false, reachable: false });
setState("idle");
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to stop Redis");
setState("error");
}
}
return (
<div className="rounded-xl border border-border bg-surface p-5">
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
<div>
<h3 className="text-base font-semibold text-text-main">
{t("redisLauncherTitle", "Local Redis")}
</h3>
<p className="mt-1 text-sm text-text-muted">
{t(
"redisLauncherDesc",
"One-click launch a Redis 7 container (Podman or Docker) for response cache, quota tracking, and rate limiting."
)}
</p>
</div>
<div className="flex flex-wrap gap-2">
<Button size="sm" variant="outline" onClick={refresh} disabled={state === "checking"}>
{state === "checking" ? "…" : t("redisLauncherRefresh", "Refresh")}
</Button>
{status?.running ? (
<Button size="sm" variant="outline" onClick={stop} disabled={state === "launching"}>
{state === "launching" ? "…" : t("redisLauncherStop", "Stop")}
</Button>
) : (
<Button size="sm" onClick={launch} disabled={state === "launching"}>
{state === "launching"
? t("redisLauncherLaunching", "Launching…")
: t("redisLauncherLaunch", "Launch Redis")}
</Button>
)}
</div>
</div>
{status && (
<dl className="mt-4 grid grid-cols-1 gap-3 text-sm md:grid-cols-3">
<Stat
label={t("redisLauncherContainer", "Container")}
value={status.exists ? "present" : "missing"}
tone={status.exists ? "ok" : "warn"}
/>
<Stat
label={t("redisLauncherRunning", "Running")}
value={status.running ? "yes" : "no"}
tone={status.running ? "ok" : "warn"}
/>
<Stat
label={t("redisLauncherReachable", "Reachable")}
value={status.reachable ? "yes" : "no"}
tone={status.reachable ? "ok" : "warn"}
/>
</dl>
)}
{error && (
<p className="mt-3 text-sm text-red-400">
{t("redisLauncherError", "Error: {{message}}", { message: error })}
</p>
)}
<p className="mt-3 text-xs text-text-muted">
{t(
"redisLauncherHint",
"Equivalent to running `omniroute redis up`. The container is named `omniroute-redis` and listens on 127.0.0.1:6379."
)}
</p>
</div>
);
}
function Stat({
label,
value,
tone,
}: {
label: string;
value: string;
tone: "ok" | "warn";
}) {
const color = tone === "ok" ? "text-emerald-400" : "text-amber-400";
return (
<div className="rounded-lg border border-border bg-bg-subtle p-3">
<dt className="text-[10px] uppercase tracking-wide text-text-muted">{label}</dt>
<dd className={`mt-1 font-mono text-sm ${color}`}>{value}</dd>
</div>
);
}