feat(parcel-sync): eTerra health check + maintenance detection
- New eterra-health.ts service: pings eTerra periodically (3min), detects maintenance (503, keywords), tracks consecutive failures - New /api/eterra/health endpoint for explicit health queries - Session route blocks login when eTerra is in maintenance (503 response) - GET /api/eterra/session now includes eterraAvailable/eterraMaintenance - ConnectionPill shows amber 'Mentenanță' state with AlertTriangle icon instead of confusing red error when eTerra is down - Auto-connect skips when maintenance detected, retries when back online - 30s session poll auto-detects recovery and re-enables auto-connect
This commit is contained in:
@@ -75,6 +75,12 @@ type SessionStatus = {
|
||||
connectedAt?: string;
|
||||
activeJobCount: number;
|
||||
activeJobPhase?: string;
|
||||
/** eTerra platform health */
|
||||
eterraAvailable?: boolean;
|
||||
/** True when eTerra is in maintenance */
|
||||
eterraMaintenance?: boolean;
|
||||
/** Human-readable health message */
|
||||
eterraHealthMessage?: string;
|
||||
};
|
||||
|
||||
type ExportProgress = {
|
||||
@@ -153,9 +159,11 @@ function ConnectionPill({
|
||||
"hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
session.connected
|
||||
? "border-emerald-200 bg-emerald-50/80 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-400"
|
||||
: connectionError
|
||||
? "border-rose-200 bg-rose-50/80 text-rose-600 dark:border-rose-800 dark:bg-rose-950/40 dark:text-rose-400"
|
||||
: "border-muted-foreground/20 bg-muted/50 text-muted-foreground",
|
||||
: session.eterraMaintenance
|
||||
? "border-amber-200 bg-amber-50/80 text-amber-600 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-400"
|
||||
: connectionError
|
||||
? "border-rose-200 bg-rose-50/80 text-rose-600 dark:border-rose-800 dark:bg-rose-950/40 dark:text-rose-400"
|
||||
: "border-muted-foreground/20 bg-muted/50 text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{connecting ? (
|
||||
@@ -165,6 +173,8 @@ function ConnectionPill({
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-500" />
|
||||
</span>
|
||||
) : session.eterraMaintenance ? (
|
||||
<AlertTriangle className="h-3 w-3" />
|
||||
) : connectionError ? (
|
||||
<WifiOff className="h-3 w-3" />
|
||||
) : (
|
||||
@@ -175,9 +185,11 @@ function ConnectionPill({
|
||||
? "Se conectează…"
|
||||
: session.connected
|
||||
? "eTerra"
|
||||
: connectionError
|
||||
? "Eroare"
|
||||
: "Deconectat"}
|
||||
: session.eterraMaintenance
|
||||
? "Mentenanță"
|
||||
: connectionError
|
||||
? "Eroare"
|
||||
: "Deconectat"}
|
||||
</span>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -214,25 +226,52 @@ function ConnectionPill({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Info when not connected */}
|
||||
{!session.connected && !connectionError && (
|
||||
<div className="px-3 py-3 text-xs text-muted-foreground">
|
||||
<p>Conexiunea se face automat când începi să scrii un UAT.</p>
|
||||
<p className="mt-1 text-[11px] opacity-70">
|
||||
Credențialele sunt preluate din configurarea serverului.
|
||||
</p>
|
||||
{/* Maintenance banner */}
|
||||
{!session.connected && session.eterraMaintenance && (
|
||||
<div className="px-3 py-3 text-xs border-b bg-amber-50/50 dark:bg-amber-950/20">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertTriangle className="h-3.5 w-3.5 text-amber-500 mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-amber-700 dark:text-amber-400">
|
||||
eTerra este în mentenanță
|
||||
</p>
|
||||
<p className="mt-1 text-[11px] text-amber-600/80 dark:text-amber-400/70">
|
||||
Platforma ANCPI nu este disponibilă momentan. Conectarea va fi
|
||||
reactivată automat când serviciul revine online.
|
||||
</p>
|
||||
{session.eterraHealthMessage && (
|
||||
<p className="mt-1 text-[10px] opacity-60 font-mono">
|
||||
{session.eterraHealthMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error detail */}
|
||||
{!session.connected && connectionError && (
|
||||
<div className="px-3 py-3 text-xs text-muted-foreground">
|
||||
<p>
|
||||
Conexiunea automată a eșuat. Verifică credențialele din
|
||||
variabilele de mediu (ETERRA_USERNAME / ETERRA_PASSWORD).
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{/* Info when not connected (and not in maintenance) */}
|
||||
{!session.connected &&
|
||||
!connectionError &&
|
||||
!session.eterraMaintenance && (
|
||||
<div className="px-3 py-3 text-xs text-muted-foreground">
|
||||
<p>Conexiunea se face automat când începi să scrii un UAT.</p>
|
||||
<p className="mt-1 text-[11px] opacity-70">
|
||||
Credențialele sunt preluate din configurarea serverului.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error detail (only when NOT maintenance — to avoid confusing users) */}
|
||||
{!session.connected &&
|
||||
connectionError &&
|
||||
!session.eterraMaintenance && (
|
||||
<div className="px-3 py-3 text-xs text-muted-foreground">
|
||||
<p>
|
||||
Conexiunea automată a eșuat. Verifică credențialele din
|
||||
variabilele de mediu (ETERRA_USERNAME / ETERRA_PASSWORD).
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Connected — active jobs info + disconnect */}
|
||||
{session.connected && (
|
||||
@@ -444,7 +483,17 @@ export function ParcelSyncModule() {
|
||||
try {
|
||||
const res = await fetch("/api/eterra/session");
|
||||
const data = (await res.json()) as SessionStatus;
|
||||
setSession(data);
|
||||
setSession((prev) => {
|
||||
// If eTerra was in maintenance but is now back online, reset auto-connect
|
||||
if (
|
||||
prev.eterraMaintenance &&
|
||||
data.eterraAvailable &&
|
||||
!data.eterraMaintenance
|
||||
) {
|
||||
autoConnectAttempted.current = false;
|
||||
}
|
||||
return data;
|
||||
});
|
||||
if (data.connected) setConnectionError("");
|
||||
return data;
|
||||
} catch {
|
||||
@@ -539,6 +588,8 @@ export function ParcelSyncModule() {
|
||||
|
||||
const triggerAutoConnect = useCallback(async () => {
|
||||
if (session.connected || connecting || autoConnectAttempted.current) return;
|
||||
// Don't attempt login when eTerra is in maintenance
|
||||
if (session.eterraMaintenance) return;
|
||||
autoConnectAttempted.current = true;
|
||||
setConnecting(true);
|
||||
setConnectionError("");
|
||||
@@ -551,9 +602,20 @@ export function ParcelSyncModule() {
|
||||
const data = (await res.json()) as {
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
maintenance?: boolean;
|
||||
};
|
||||
if (data.success) {
|
||||
await fetchSession();
|
||||
} else if (data.maintenance) {
|
||||
// eTerra is in maintenance — set flag, DON'T show as connection error
|
||||
setSession((prev) => ({
|
||||
...prev,
|
||||
eterraMaintenance: true,
|
||||
eterraAvailable: false,
|
||||
eterraHealthMessage: data.error ?? "eTerra în mentenanță",
|
||||
}));
|
||||
// Allow retry later when maintenance ends
|
||||
autoConnectAttempted.current = false;
|
||||
} else {
|
||||
setConnectionError(data.error ?? "Eroare conectare");
|
||||
}
|
||||
@@ -561,7 +623,7 @@ export function ParcelSyncModule() {
|
||||
setConnectionError("Eroare rețea");
|
||||
}
|
||||
setConnecting(false);
|
||||
}, [session.connected, connecting, fetchSession]);
|
||||
}, [session.connected, session.eterraMaintenance, connecting, fetchSession]);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════ */
|
||||
/* Disconnect */
|
||||
|
||||
Reference in New Issue
Block a user