Files
Business-Management/frontend/src/pages/AppsPage.tsx
T
curo1305 151773ab51 Fix health check loop silently dying on uncaught exception
Wrap check_all() call inside the loop with try/except so a transient error
cannot exit the while-True and freeze all health statuses. Add transition
logging (HEALTHY / UNHEALTHY) so docker logs show when a service changes
state. Also add refetchIntervalInBackground on the frontend query so the
poll continues even when the browser tab is not focused.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 17:36:58 +02:00

104 lines
3.6 KiB
TypeScript

import { Link } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { getMe, getServices } from "../api/client";
const cardBase: React.CSSProperties = {
backgroundColor: "rgb(var(--color-surface))",
border: "1px solid rgb(var(--color-border))",
borderRadius: 8,
padding: 24,
width: 280,
display: "flex",
flexDirection: "column",
gap: 12,
};
const clickableCard: React.CSSProperties = {
...cardBase,
cursor: "pointer",
textDecoration: "none",
color: "inherit",
transition: "box-shadow 150ms, border-color 150ms",
};
const unavailableCard: React.CSSProperties = {
...cardBase,
opacity: 0.6,
};
export default function AppsPage() {
const { data: user } = useQuery({ queryKey: ["me"], queryFn: getMe });
const { data: services = [] } = useQuery({
queryKey: ["services"],
queryFn: getServices,
refetchInterval: 30_000,
refetchIntervalInBackground: true,
});
return (
<div style={{ padding: 32, maxWidth: 900, margin: "0 auto" }}>
<h1>Apps</h1>
<div style={{ display: "flex", gap: 24, flexWrap: "wrap", marginTop: 24 }}>
{services.map((svc) => {
const canOpen = svc.healthy && !!svc.app_path;
const CardWrapper = canOpen ? Link : "div";
const wrapperProps = canOpen
? {
to: svc.app_path,
style: clickableCard,
onMouseEnter: (e: React.MouseEvent<HTMLAnchorElement>) => {
e.currentTarget.style.boxShadow = "0 4px 12px rgb(0 0 0 / 0.12)";
e.currentTarget.style.borderColor = "rgb(var(--color-primary))";
},
onMouseLeave: (e: React.MouseEvent<HTMLAnchorElement>) => {
e.currentTarget.style.boxShadow = "";
e.currentTarget.style.borderColor = "rgb(var(--color-border))";
},
}
: { style: svc.healthy ? cardBase : unavailableCard };
return (
<CardWrapper key={svc.id} {...(wrapperProps as any)}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<h2 style={{ margin: 0, fontSize: 18 }}>{svc.name}</h2>
{svc.healthy ? (
<span style={{ fontSize: 12, color: "#2a9d8f", fontWeight: 600 }}>Available</span>
) : (
<span style={{ fontSize: 12, color: "#e76f51", fontWeight: 600 }}>Unavailable</span>
)}
</div>
<p style={{ margin: 0, color: "rgb(var(--color-text-muted))", fontSize: 14 }}>
{svc.description}
</p>
{!svc.healthy && (
<p style={{ margin: 0, fontSize: 12, color: "#e76f51" }}>
This service is currently unavailable. Please try again later or contact your administrator.
</p>
)}
<div style={{ display: "flex", gap: 8, marginTop: "auto" }}>
{user?.is_admin && svc.settings_path && (
<Link
to={svc.settings_path}
onClick={(e) => e.stopPropagation()}
style={{
padding: "6px 14px",
border: "1px solid #ccc",
borderRadius: 4,
textDecoration: "none",
fontSize: 14,
color: "#333",
}}
title="Settings"
>
Settings
</Link>
)}
</div>
</CardWrapper>
);
})}
</div>
</div>
);
}