mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-15 18:15:49 +00:00
feat(v5): sync server status and ingress access
Add Flux agent token issuing, open and revoke Caddy ingress firewall rules, broadcast server-scoped application status updates, and update the dashboard to show unreachable servers as unknown.
This commit is contained in:
1 parent
75cbcad240
commit
e3e91b7741
14 files changed
+557
-47
No files matched your search
@@ -30,6 +30,7 @@ type ConnectionEndpoint = {
|
||||
|
||||
type V5CanvasResourceUpdatedEvent = {
|
||||
application: V5Application | null;
|
||||
applications?: V5Application[];
|
||||
caddyIngress: V5CaddyIngress | null;
|
||||
};
|
||||
|
||||
@@ -117,6 +118,10 @@ function statusBadgeClass(status: string): string | false {
|
||||
return 'bg-warning/15 text-warning';
|
||||
}
|
||||
|
||||
if (status === 'unknown') {
|
||||
return 'bg-muted text-muted-foreground';
|
||||
}
|
||||
|
||||
if (['failed', 'exited', 'unreachable'].includes(status)) {
|
||||
return 'bg-destructive/15 text-destructive';
|
||||
}
|
||||
@@ -185,6 +190,7 @@ export default function Dashboard({
|
||||
const [ingressModal, setIngressModal] = useState<IngressModalState | null>(null);
|
||||
const [isSavingIngress, setIsSavingIngress] = useState(false);
|
||||
const [savingIngressApplicationId, setSavingIngressApplicationId] = useState<string | null>(null);
|
||||
const [deletingApplicationIds, setDeletingApplicationIds] = useState<Set<string>>(() => new Set());
|
||||
const canvasRef = useRef<HTMLDivElement | null>(null);
|
||||
const hasCanvasNodes = applications.length > 0 || ingresses.length > 0;
|
||||
|
||||
@@ -192,7 +198,7 @@ export default function Dashboard({
|
||||
() => ({
|
||||
running: applications.filter((application) => application.effectiveStatus === 'running').length,
|
||||
failed: applications.filter((application) => application.effectiveStatus === 'failed').length,
|
||||
unreachable: applications.filter((application) => application.effectiveStatus === 'unreachable').length,
|
||||
unknown: applications.filter((application) => application.effectiveStatus === 'unknown').length,
|
||||
}),
|
||||
[applications],
|
||||
);
|
||||
@@ -259,6 +265,16 @@ export default function Dashboard({
|
||||
);
|
||||
}
|
||||
|
||||
if (event.applications && event.applications.length > 0) {
|
||||
setApplications((currentApplications) =>
|
||||
currentApplications.map((application) => {
|
||||
const updatedApplication = event.applications?.find((candidate) => candidate.id === application.id);
|
||||
|
||||
return updatedApplication ?? application;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (event.caddyIngress) {
|
||||
setIngresses((currentIngresses) =>
|
||||
currentIngresses.map((ingress) =>
|
||||
@@ -660,6 +676,7 @@ function normalizeConnection(connection: V5ResourceConnection): CanvasConnection
|
||||
|
||||
async function removeApplication(application: V5Application): Promise<void> {
|
||||
setNotice(null);
|
||||
setDeletingApplicationIds((currentIds) => new Set(currentIds).add(application.id));
|
||||
|
||||
|
||||
try {
|
||||
@@ -689,6 +706,14 @@ function normalizeConnection(connection: V5ResourceConnection): CanvasConnection
|
||||
);
|
||||
} catch (error) {
|
||||
setNotice(error instanceof Error ? error.message : 'Could not delete application.');
|
||||
} finally {
|
||||
setDeletingApplicationIds((currentIds) => {
|
||||
const nextIds = new Set(currentIds);
|
||||
|
||||
nextIds.delete(application.id);
|
||||
|
||||
return nextIds;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1329,10 +1354,10 @@ function normalizeConnection(connection: V5ResourceConnection): CanvasConnection
|
||||
<span className="text-destructive">{statusCounts.failed} failed</span>
|
||||
</>
|
||||
)}
|
||||
{statusCounts.unreachable > 0 && (
|
||||
{statusCounts.unknown > 0 && (
|
||||
<>
|
||||
<span>•</span>
|
||||
<span className="text-destructive">{statusCounts.unreachable} unreachable</span>
|
||||
<span>{statusCounts.unknown} unknown</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -1623,18 +1648,21 @@ function normalizeConnection(connection: V5ResourceConnection): CanvasConnection
|
||||
);
|
||||
})}
|
||||
|
||||
{applications.map((application) => (
|
||||
<div
|
||||
key={application.id}
|
||||
data-application-card="application-card"
|
||||
data-application-id={application.id}
|
||||
className="group/application absolute min-h-[8.5rem] w-80 select-none overflow-visible rounded-xl border border-border bg-card p-4 shadow-xl transition-shadow hover:shadow-2xl"
|
||||
style={{
|
||||
transform: `translate3d(${application.canvasX}px, ${application.canvasY}px, 0)`,
|
||||
}}
|
||||
onPointerDown={(event) => startApplicationDrag(event, application)}
|
||||
onDoubleClick={(event) => openApplicationInspector(event, application)}
|
||||
>
|
||||
{applications.map((application) => {
|
||||
const isDeletingApplication = deletingApplicationIds.has(application.id);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={application.id}
|
||||
data-application-card="application-card"
|
||||
data-application-id={application.id}
|
||||
className="group/application absolute min-h-[8.5rem] w-80 select-none overflow-visible rounded-xl border border-border bg-card p-4 shadow-xl transition-shadow hover:shadow-2xl"
|
||||
style={{
|
||||
transform: `translate3d(${application.canvasX}px, ${application.canvasY}px, 0)`,
|
||||
}}
|
||||
onPointerDown={(event) => startApplicationDrag(event, application)}
|
||||
onDoubleClick={(event) => openApplicationInspector(event, application)}
|
||||
>
|
||||
{CONNECTOR_SIDES.map((side) => (
|
||||
<button
|
||||
key={side}
|
||||
@@ -1687,9 +1715,10 @@ function normalizeConnection(connection: V5ResourceConnection): CanvasConnection
|
||||
event.stopPropagation();
|
||||
void removeApplication(application);
|
||||
}}
|
||||
className="rounded-md border border-destructive/40 px-2 py-1 text-[0.625rem] font-semibold uppercase tracking-wide text-destructive transition hover:bg-destructive/10"
|
||||
disabled={isDeletingApplication}
|
||||
className="rounded-md border border-destructive/40 px-2 py-1 text-[0.625rem] font-semibold uppercase tracking-wide text-destructive transition hover:bg-destructive/10 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
Delete
|
||||
{isDeletingApplication ? 'Deleting…' : 'Delete'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1722,8 +1751,9 @@ function normalizeConnection(connection: V5ResourceConnection): CanvasConnection
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -92,7 +92,7 @@ export type V5Application = {
|
||||
containerName: string;
|
||||
status: 'creating' | 'running' | 'failed' | string;
|
||||
statusMessage: string | null;
|
||||
effectiveStatus: 'creating' | 'running' | 'failed' | 'unreachable' | string;
|
||||
effectiveStatus: 'creating' | 'running' | 'failed' | 'unknown' | string;
|
||||
effectiveStatusMessage: string | null;
|
||||
runtimeContainerId: string | null;
|
||||
serverName: string | null;
|
||||
|
||||
Reference in new issue
Block a user