feat: move deploy buttons into service cards, fix siteMonitor URLs
- Create src/widgets/deploy/component.jsx — service-card widget variant
that receives { service } prop (not { options }) for use in services.yaml
- Register deploy in src/widgets/components.js
- Move deploy widgets from config/widgets.yaml into each service card
via widget: type: deploy — buttons now live inline under each app
- Fix Deploy API siteMonitor URL to use Apache-proxied path
- Reduce Widgets layout columns to 2 (only resources + datetime remain)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
370ea5dae9
commit
7e6d8517b0
5 changed files with 105 additions and 26 deletions
|
|
@ -8,6 +8,10 @@
|
|||
server: local
|
||||
siteMonitor: https://optical-dev.oliver.solutions/ppt-tool
|
||||
showStats: true
|
||||
widget:
|
||||
type: deploy
|
||||
service: ppt-tool
|
||||
label: DeckForge
|
||||
|
||||
- GMAL Scope Builder:
|
||||
icon: mdi-briefcase-outline
|
||||
|
|
@ -17,6 +21,10 @@
|
|||
server: local
|
||||
siteMonitor: https://optical-dev.oliver.solutions/gsb
|
||||
showStats: true
|
||||
widget:
|
||||
type: deploy
|
||||
service: gmal-scope-builder
|
||||
label: Scope Builder
|
||||
|
||||
- Semblance:
|
||||
icon: mdi-account-group-outline
|
||||
|
|
@ -26,6 +34,10 @@
|
|||
server: local
|
||||
siteMonitor: https://optical-dev.oliver.solutions/semblance
|
||||
showStats: true
|
||||
widget:
|
||||
type: deploy
|
||||
service: semblance
|
||||
label: Semblance
|
||||
|
||||
- CC Dashboard:
|
||||
icon: mdi-view-dashboard-outline
|
||||
|
|
@ -35,6 +47,10 @@
|
|||
server: local
|
||||
siteMonitor: https://optical-dev.oliver.solutions/cc-dashboard
|
||||
showStats: true
|
||||
widget:
|
||||
type: deploy
|
||||
service: cc-dashboard
|
||||
label: CC Dashboard
|
||||
|
||||
- OliVAS:
|
||||
icon: mdi-robot-outline
|
||||
|
|
@ -44,6 +60,10 @@
|
|||
server: local
|
||||
siteMonitor: https://optical-dev.oliver.solutions/api/health
|
||||
showStats: true
|
||||
widget:
|
||||
type: deploy
|
||||
service: olivas
|
||||
label: OliVAS
|
||||
|
||||
- Infrastructure:
|
||||
- Homepage:
|
||||
|
|
@ -54,12 +74,16 @@
|
|||
server: local
|
||||
siteMonitor: https://optical-dev.oliver.solutions/homepage
|
||||
showStats: true
|
||||
widget:
|
||||
type: deploy
|
||||
service: homepage
|
||||
label: Homepage
|
||||
|
||||
- Deploy API:
|
||||
icon: mdi-rocket-launch-outline
|
||||
href: https://optical-dev.oliver.solutions/deploy-api/docs
|
||||
description: One-click deploy service
|
||||
siteMonitor: http://127.0.0.1:9000/services
|
||||
siteMonitor: https://optical-dev.oliver.solutions/deploy-api/services
|
||||
|
||||
- PostgreSQL × 4:
|
||||
icon: mdi-database-outline
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ target: _blank
|
|||
layout:
|
||||
Widgets:
|
||||
style: row
|
||||
columns: 9
|
||||
columns: 2
|
||||
AI Tools:
|
||||
style: row
|
||||
columns: 5
|
||||
|
|
|
|||
|
|
@ -13,27 +13,3 @@
|
|||
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
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const components = {
|
|||
iframe: dynamic(() => import("./iframe/component")),
|
||||
customapi: dynamic(() => import("./customapi/component")),
|
||||
deluge: dynamic(() => import("./deluge/component")),
|
||||
deploy: dynamic(() => import("./deploy/component")),
|
||||
develancacheui: dynamic(() => import("./develancacheui/component")),
|
||||
diskstation: dynamic(() => import("./diskstation/component")),
|
||||
dispatcharr: dynamic(() => import("./dispatcharr/component")),
|
||||
|
|
|
|||
78
src/widgets/deploy/component.jsx
Normal file
78
src/widgets/deploy/component.jsx
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
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 DeployComponent({ service }) {
|
||||
const { service: svcName, label, apiBase = "/deploy-api" } = service?.widget ?? {};
|
||||
const bp = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
|
||||
// SWR middleware already prepends bp, so statusUrl must NOT include it
|
||||
const statusUrl = svcName ? `${apiBase}/status/${svcName}` : 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/${svcName}`, { method: "POST" });
|
||||
await mutate();
|
||||
} finally {
|
||||
setTriggering(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!svcName) return null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center justify-between px-2 py-1 gap-2 w-full">
|
||||
<div className="flex flex-col">
|
||||
<span className={`text-xs font-semibold ${STATUS_COLORS[status] ?? STATUS_COLORS.idle}`}>
|
||||
{isRunning ? <span className="animate-pulse">{STATUS_LABELS.running}</span> : (STATUS_LABELS[status] ?? status)}
|
||||
</span>
|
||||
{lastRun && (
|
||||
<span className="text-theme-500 dark:text-theme-400 text-xs opacity-60">{lastRun}</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDeploy}
|
||||
disabled={isRunning || triggering}
|
||||
className={[
|
||||
"px-3 py-1 rounded text-xs font-medium transition-colors shrink-0",
|
||||
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 ?? svcName}`}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue