feat(wds): live status banner, auto-poll, and instant error emails
- Auto-poll every 15s when sync is running, 60s when idle - Live status banner: running (with city/step), error list, weekend window waiting, connection error - Highlight active city card and currently-running step with pulse animation - Send immediate error email per failed step (not just at session end) - Expose syncStatus/currentActivity/inWeekendWindow in API response - Stop silently swallowing fetch/action errors — show them in the UI Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,22 @@ import { sendEmail } from "@/core/notifications/email-service";
|
||||
const prisma = new PrismaClient();
|
||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Live activity tracking (globalThis — same process) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
const g = globalThis as {
|
||||
__weekendSyncActivity?: {
|
||||
city: string;
|
||||
step: string;
|
||||
startedAt: string;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export function getWeekendSyncActivity() {
|
||||
return g.__weekendSyncActivity ?? null;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* City queue configuration */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -315,6 +331,7 @@ export async function runWeekendDeepSync(): Promise<void> {
|
||||
// Check time window
|
||||
if (!stillInWindow()) {
|
||||
console.log("[weekend-sync] Fereastra s-a inchis, opresc.");
|
||||
g.__weekendSyncActivity = null;
|
||||
await saveState(state);
|
||||
await sendStatusEmail(state, log, sessionStart);
|
||||
return;
|
||||
@@ -323,6 +340,7 @@ export async function runWeekendDeepSync(): Promise<void> {
|
||||
// Check eTerra health
|
||||
if (!isEterraAvailable()) {
|
||||
console.log("[weekend-sync] eTerra indisponibil, opresc.");
|
||||
g.__weekendSyncActivity = null;
|
||||
await saveState(state);
|
||||
await sendStatusEmail(state, log, sessionStart);
|
||||
return;
|
||||
@@ -339,11 +357,19 @@ export async function runWeekendDeepSync(): Promise<void> {
|
||||
|
||||
// Execute step — fresh client per step (sessions expire after ~10 min)
|
||||
console.log(`[weekend-sync] ${city.name}: ${stepName}...`);
|
||||
g.__weekendSyncActivity = {
|
||||
city: city.name,
|
||||
step: stepName,
|
||||
startedAt: new Date().toISOString(),
|
||||
};
|
||||
try {
|
||||
const client = await EterraClient.create(username, password);
|
||||
const result = await executeStep(city, stepName, client);
|
||||
city.steps[stepName] = result.success ? "done" : "error";
|
||||
if (!result.success) city.errorMessage = result.message;
|
||||
if (!result.success) {
|
||||
city.errorMessage = result.message;
|
||||
await sendStepErrorEmail(city, stepName, result.message);
|
||||
}
|
||||
city.lastActivity = new Date().toISOString();
|
||||
log.push({
|
||||
city: city.name,
|
||||
@@ -368,7 +394,9 @@ export async function runWeekendDeepSync(): Promise<void> {
|
||||
console.error(
|
||||
`[weekend-sync] ${city.name}: ${stepName} EROARE: ${msg}`,
|
||||
);
|
||||
await sendStepErrorEmail(city, stepName, msg);
|
||||
}
|
||||
g.__weekendSyncActivity = null;
|
||||
|
||||
stepsCompleted++;
|
||||
// Save state after each step (crash safety)
|
||||
@@ -400,6 +428,70 @@ export async function runWeekendDeepSync(): Promise<void> {
|
||||
console.log(`[weekend-sync] Sesiune finalizata. ${stepsCompleted} pasi executati.`);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Immediate error email */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
async function sendStepErrorEmail(
|
||||
city: CityState,
|
||||
step: StepName,
|
||||
errorMsg: string,
|
||||
): Promise<void> {
|
||||
const emailTo = process.env.WEEKEND_SYNC_EMAIL;
|
||||
if (!emailTo) return;
|
||||
|
||||
try {
|
||||
const now = new Date();
|
||||
const timeStr = now.toLocaleString("ro-RO", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
const stepLabel: Record<StepName, string> = {
|
||||
sync_terenuri: "Sync Terenuri",
|
||||
sync_cladiri: "Sync Cladiri",
|
||||
import_nogeom: "Import No-Geom",
|
||||
enrich: "Enrichment",
|
||||
};
|
||||
|
||||
const html = `
|
||||
<div style="font-family:system-ui,sans-serif;max-width:600px;margin:0 auto">
|
||||
<h2 style="color:#ef4444;margin-bottom:4px">Weekend Sync — Eroare</h2>
|
||||
<p style="color:#6b7280;margin-top:0">${timeStr}</p>
|
||||
<table style="border-collapse:collapse;width:100%;border:1px solid #fecaca;border-radius:6px;background:#fef2f2">
|
||||
<tr>
|
||||
<td style="padding:8px 12px;font-weight:600;color:#374151">Oras</td>
|
||||
<td style="padding:8px 12px">${city.name} (${city.county})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 12px;font-weight:600;color:#374151">Pas</td>
|
||||
<td style="padding:8px 12px">${stepLabel[step]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 12px;font-weight:600;color:#374151">Eroare</td>
|
||||
<td style="padding:8px 12px;color:#dc2626;word-break:break-word">${errorMsg}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="color:#9ca3af;font-size:11px;margin-top:16px">
|
||||
Generat automat de ArchiTools Weekend Sync
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
await sendEmail({
|
||||
to: emailTo,
|
||||
subject: `[ArchiTools] WDS Eroare: ${city.name} — ${stepLabel[step]}`,
|
||||
html,
|
||||
});
|
||||
console.log(`[weekend-sync] Email eroare trimis: ${city.name}/${step}`);
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.warn(`[weekend-sync] Nu s-a putut trimite email eroare: ${msg}`);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Email status report */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
Reference in New Issue
Block a user