Files
flatrender/src/lib/render-service.ts
T
soroush.asadi 61ba526122 feat(admin): render-engine kill switch (block renders + show message)
Lets an admin disable rendering when no render node is available — users can't
start new renders and see a localized "service unavailable until <date>" message.

- Admin → فارم رندر → موتور رندر (RenderEngineAdmin): on/off toggle + fa/en message
  + optional Jalali "until" date; saved as one `render_service` Website Setting
  (jsonb) via /v1/settings — no backend change, no migration.
- lib/render-service.ts: fetchRenderServiceStatus (fail-open) + renderServiceMessage
  (locale + appends the date).
- Enforcement: POST /api/render returns 503 {code:render_disabled, messages} when off;
  studio render page reads GET /api/render/service on mount → disables "شروع رندر"
  and shows the banner, and handles the 503 on click.
- i18n: appAdminLayout.renderEngine (fa+en, parity 1045/1045). tsc + next build clean.
  Verified: disabled setting → /api/render/service returns enabled:false.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 09:47:42 +03:30

63 lines
2.1 KiB
TypeScript

/**
* Render-engine kill switch. Admins can disable new renders (e.g. when no render
* node is available) via the `render_service` Website Setting; the studio then
* blocks "start render" and shows a localized "unavailable until <date>" message.
*
* Stored as one jsonb setting — no backend/migration. Reads fail OPEN (renders
* stay enabled) so a settings hiccup never blocks a working render farm.
*/
import { gatewayUrl } from "@/lib/api/gateway";
export const RENDER_SERVICE_KEY = "render_service";
export interface RenderServiceStatus {
enabled: boolean;
messageFa?: string;
messageEn?: string;
untilDate?: string; // ISO date (optional)
}
export async function fetchRenderServiceStatus(): Promise<RenderServiceStatus> {
try {
const res = await fetch(gatewayUrl("/v1/settings/"), {
cache: "no-store",
headers: { Accept: "application/json" },
});
if (!res.ok) return { enabled: true };
const rows = (await res.json()) as Array<{ key: string; value: string }>;
const row = Array.isArray(rows) ? rows.find((r) => r.key === RENDER_SERVICE_KEY) : null;
if (!row?.value) return { enabled: true };
const v = typeof row.value === "string" ? JSON.parse(row.value) : row.value;
return {
enabled: v?.enabled !== false,
messageFa: v?.messageFa ?? undefined,
messageEn: v?.messageEn ?? undefined,
untilDate: v?.untilDate ?? undefined,
};
} catch {
return { enabled: true }; // fail open
}
}
/** Pick the localized message, appending the "until" date when present. */
export function renderServiceMessage(
status: RenderServiceStatus,
locale: string,
fallback: string,
): string {
const base = (locale === "fa" ? status.messageFa : status.messageEn) || status.messageEn || status.messageFa || fallback;
if (status.untilDate) {
try {
const d = new Intl.DateTimeFormat(locale === "fa" ? "fa-IR" : "en-US", {
year: "numeric",
month: "long",
day: "numeric",
}).format(new Date(status.untilDate));
return `${base} (${d})`;
} catch {
return base;
}
}
return base;
}