Feat: add deploy widget with one-click deploy buttons
- components/widgets/deploy/deploy.jsx: new widget with deploy button,
status polling (2s when running, 10s idle), last deploy time display
- widget.jsx: register 'deploy' widget type
- config/widgets.yaml: add deploy buttons for all 6 server services
Deploy API runs as systemd service on host :9000, proxied via Apache
at /deploy-api/. Widget polls GET /status/{service} and triggers
POST /deploy/{service} on button click.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5eb4a2908d
commit
cd66562e9d
3 changed files with 112 additions and 0 deletions
|
|
@ -12,3 +12,27 @@
|
|||
timeStyle: short
|
||||
dateStyle: short
|
||||
hourCycle: h23
|
||||
|
||||
- deploy:
|
||||
service: ppt-tool
|
||||
label: DeckForge
|
||||
|
||||
- deploy:
|
||||
service: semblance
|
||||
label: Semblance
|
||||
|
||||
- deploy:
|
||||
service: olivas
|
||||
label: OliVAS
|
||||
|
||||
- deploy:
|
||||
service: gmal-scope-builder
|
||||
label: Scope Builder
|
||||
|
||||
- deploy:
|
||||
service: cc-dashboard
|
||||
label: CC Dashboard
|
||||
|
||||
- deploy:
|
||||
service: homepage
|
||||
label: Homepage
|
||||
|
|
|
|||
87
src/components/widgets/deploy/deploy.jsx
Normal file
87
src/components/widgets/deploy/deploy.jsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import { useState } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
const STATUS_COLORS = {
|
||||
idle: "text-theme-500 dark:text-theme-400",
|
||||
running: "text-blue-500 dark:text-blue-400",
|
||||
success: "text-emerald-500 dark:text-emerald-400",
|
||||
failed: "text-red-500 dark:text-red-400",
|
||||
};
|
||||
|
||||
const STATUS_LABELS = {
|
||||
idle: "Never deployed",
|
||||
running: "Deploying...",
|
||||
success: "Deployed",
|
||||
failed: "Failed",
|
||||
};
|
||||
|
||||
function formatTime(iso) {
|
||||
if (!iso) return null;
|
||||
const d = new Date(iso);
|
||||
return d.toLocaleString("en-GB", { dateStyle: "short", timeStyle: "short", hourCycle: "h23" });
|
||||
}
|
||||
|
||||
export default function Deploy({ options }) {
|
||||
const { service, label, apiBase = "/deploy-api" } = options ?? {};
|
||||
const bp = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
|
||||
const statusUrl = service ? `${bp}${apiBase}/status/${service}` : null;
|
||||
|
||||
const { data, mutate } = useSWR(statusUrl, {
|
||||
refreshInterval: (d) => (d?.status === "running" ? 2000 : 10000),
|
||||
});
|
||||
|
||||
const [triggering, setTriggering] = useState(false);
|
||||
|
||||
const status = data?.status ?? "idle";
|
||||
const lastRun = data?.last_run ? formatTime(data.last_run) : null;
|
||||
const isRunning = status === "running";
|
||||
|
||||
const handleDeploy = async () => {
|
||||
if (isRunning || triggering) return;
|
||||
setTriggering(true);
|
||||
try {
|
||||
await fetch(`${bp}${apiBase}/deploy/${service}`, { method: "POST" });
|
||||
await mutate();
|
||||
} finally {
|
||||
setTriggering(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!service) {
|
||||
return (
|
||||
<div className="flex flex-col items-center text-theme-500 text-xs p-2">
|
||||
<span>No service configured</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center p-2 gap-1 w-full">
|
||||
<div className={`text-xs font-semibold ${STATUS_COLORS[status] ?? STATUS_COLORS.idle}`}>
|
||||
{isRunning ? (
|
||||
<span className="animate-pulse">{STATUS_LABELS.running}</span>
|
||||
) : (
|
||||
STATUS_LABELS[status] ?? status
|
||||
)}
|
||||
</div>
|
||||
|
||||
{lastRun && (
|
||||
<div className="text-theme-500 dark:text-theme-400 text-xs opacity-75">{lastRun}</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDeploy}
|
||||
disabled={isRunning || triggering}
|
||||
className={[
|
||||
"mt-1 px-3 py-1 rounded text-xs font-medium transition-colors",
|
||||
isRunning || triggering
|
||||
? "bg-theme-300 dark:bg-theme-600 text-theme-500 dark:text-theme-400 cursor-not-allowed"
|
||||
: "bg-theme-500 hover:bg-theme-600 dark:bg-theme-600 dark:hover:bg-theme-500 text-white cursor-pointer",
|
||||
].join(" ")}
|
||||
>
|
||||
{isRunning ? "Running..." : `Deploy ${label ?? service}`}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ const widgetMappings = {
|
|||
longhorn: dynamic(() => import("components/widgets/longhorn/longhorn")),
|
||||
kubernetes: dynamic(() => import("components/widgets/kubernetes/kubernetes")),
|
||||
stocks: dynamic(() => import("components/widgets/stocks/stocks")),
|
||||
deploy: dynamic(() => import("components/widgets/deploy/deploy"), { ssr: false }),
|
||||
};
|
||||
|
||||
export default function Widget({ widget, style }) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue