61ba526122
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>
63 lines
2.1 KiB
TypeScript
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;
|
|
}
|