This commit is contained in:
ponzischeme89
2026-08-22 21:47:54 +12:00
parent d2f7f84cbf
commit 9a1f44da46
27 changed files with 452 additions and 110 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -13,7 +13,7 @@
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='16' fill='%2352b54b'/%3E%3Ctext x='16' y='23' font-family='system-ui,sans-serif' font-size='19' font-weight='800' text-anchor='middle' fill='%2306240a'%3EM%3C/text%3E%3C/svg%3E"
/>
<script type="module" crossorigin src="/admin/assets/index-BQVB9Fvj.js"></script>
<script type="module" crossorigin src="/admin/assets/index-C6xrkGsh.js"></script>
<link rel="modulepreload" crossorigin href="/admin/assets/router-D9WH5XEU.js">
<link rel="stylesheet" crossorigin href="/admin/assets/index-BIMcejkS.css">
</head>
+6 -3
View File
@@ -757,15 +757,16 @@ export interface GoroutineReport {
/* ---------- gateway settings ---------- */
/** GatewaySettings is the operator's overrides. Every field is optional in meaning: an
* empty string or a zero means "whatever the container was started with", and -1 means
* off for the three that can be switched off. */
/** GatewaySettings is the operator's server document. Environment-backed fields use an
* empty string or zero for "whatever the container was started with"; notification
* display is native to this document and defaults to home only. */
export interface GatewaySettings {
timezone: string;
logLevel: string;
sessionIdleDays: number;
sonarrAlertMinutes: number;
radarrAlertMinutes: number;
notificationDisplay: string;
embyHealthSeconds: number;
slowRequestMillis: number;
librarySyncMinutes: number;
@@ -781,6 +782,7 @@ export interface GatewaySettingValues {
sessionIdleDays: number;
sonarrAlertMinutes: number;
radarrAlertMinutes: number;
notificationDisplay: string;
embyHealthSeconds: number;
slowRequestMillis: number;
librarySyncMinutes: number;
@@ -791,6 +793,7 @@ export interface GatewaySettingsResponse {
deployed: GatewaySettingValues;
effective: GatewaySettingValues;
logLevels: string[] | null;
notificationDisplays: string[] | null;
version: string;
}
+40 -13
View File
@@ -13,10 +13,9 @@ import { Banner, Button, Card, Field, KeyValue, Loading, Note, PageHead, Tag } f
* has not been switched on. That is why it is reached from the account menu rather than
* from the rail: it belongs beside "signed in as", not beside the household's content.
*
* The whole page is an amendment to `.env`. Every field is blank by default and blank
* means "whatever this container was started with", which is printed beside it so an
* operator can always see what they are overriding, and clearing a field is a real undo
* rather than a value they have to remember. */
* Most fields amend `.env`: blank means "whatever this container was started with", which
* is printed beside the field. Notification display is server-native instead and has a
* Home-only default. */
/** OVERRIDE_OFF is the wire value for "switched off", which the three window settings need
* to distinguish from an empty field. See store.GatewaySettingsOff. */
@@ -46,12 +45,21 @@ function describe(value: number, unit: string): string {
return `${value} ${unit}${value === 1 ? '' : 's'}`;
}
function notificationDisplayLabel(value: string): string {
switch (value) {
case 'everywhere': return 'Everywhere';
case 'off': return 'Off';
default: return 'Home only';
}
}
interface Draft {
timezone: string;
logLevel: string;
sessionIdleDays: string;
sonarrAlertMinutes: string;
radarrAlertMinutes: string;
notificationDisplay: string;
embyHealthSeconds: string;
slowRequestMillis: string;
librarySyncMinutes: string;
@@ -64,6 +72,7 @@ function draftFrom(settings: GatewaySettings): Draft {
sessionIdleDays: numberFieldValue(settings.sessionIdleDays),
sonarrAlertMinutes: numberFieldValue(settings.sonarrAlertMinutes),
radarrAlertMinutes: numberFieldValue(settings.radarrAlertMinutes),
notificationDisplay: settings.notificationDisplay || 'home_only',
embyHealthSeconds: numberFieldValue(settings.embyHealthSeconds),
slowRequestMillis: numberFieldValue(settings.slowRequestMillis),
librarySyncMinutes: numberFieldValue(settings.librarySyncMinutes),
@@ -95,6 +104,7 @@ export function SettingsPage() {
sessionIdleDays: parseNumberField(draft.sessionIdleDays, false),
sonarrAlertMinutes: parseNumberField(draft.sonarrAlertMinutes, true),
radarrAlertMinutes: parseNumberField(draft.radarrAlertMinutes, true),
notificationDisplay: draft.notificationDisplay,
embyHealthSeconds: parseNumberField(draft.embyHealthSeconds, true),
slowRequestMillis: parseNumberField(draft.slowRequestMillis, true),
librarySyncMinutes: parseNumberField(draft.librarySyncMinutes, true),
@@ -117,9 +127,9 @@ export function SettingsPage() {
() => api.post<GatewaySettingsResponse>('/admin/api/gateway-settings', {
timezone: '', logLevel: '', sessionIdleDays: 0,
sonarrAlertMinutes: 0, radarrAlertMinutes: 0, embyHealthSeconds: 0, slowRequestMillis: 0,
librarySyncMinutes: 0,
librarySyncMinutes: 0, notificationDisplay: 'home_only',
}),
'Every setting is back to what this container was deployed with.',
'Every setting is back to its deployed or server default.',
);
if (saved) setDraft(draftFrom(saved.settings));
await reload();
@@ -128,6 +138,7 @@ export function SettingsPage() {
const deployed = data?.deployed;
const effective = data?.effective;
const levels = data?.logLevels ?? [];
const notificationDisplays = data?.notificationDisplays ?? ['everywhere', 'home_only', 'off'];
return (
<>
@@ -158,13 +169,14 @@ export function SettingsPage() {
{ label: 'Catalogue sweep', value: describe(effective.librarySyncMinutes, 'minute') },
{ label: 'Episode alert window', value: describe(effective.sonarrAlertMinutes, 'minute') },
{ label: 'Film alert window', value: describe(effective.radarrAlertMinutes, 'minute') },
{ label: 'Notification display', value: notificationDisplayLabel(effective.notificationDisplay) },
]}
/>
</Card>
<Card
title="Overrides"
intro="Leave a field empty to use the value this container was deployed with, shown beneath it. Changes take effect immediately — nothing here needs a restart."
title="Server settings"
intro="Environment-backed fields can be left empty to use the deployed value shown beneath them. Changes take effect immediately — nothing here needs a restart."
icon="sliders"
tone="note"
footer={
@@ -173,7 +185,7 @@ export function SettingsPage() {
Save settings
</Button>
<Button busy={busy === 'clear'} onClick={() => void clearAll()}>
Use deployed values
Use defaults
</Button>
</>
}
@@ -262,6 +274,22 @@ export function SettingsPage() {
</Field>
</div>
<div className="fields">
<Field
label="Notification display"
hint="Controls informational banners for every viewer and television. Everywhere also permits playback overlays; Home only keeps them off active movies, episodes and trailers; Off hides them throughout Memby."
>
<select
value={draft.notificationDisplay}
onChange={(event) => set('notificationDisplay', event.target.value)}
>
{notificationDisplays.map((value) => (
<option key={value} value={value}>{notificationDisplayLabel(value)}</option>
))}
</select>
</Field>
</div>
<div className="fields">
<Field
label="Episode alert window (minutes)"
@@ -291,10 +319,9 @@ export function SettingsPage() {
</div>
<Note tone="note">
These override the deployed configuration in the database, so they survive a
restart but a deployment rewrites <code>.env</code>, not this, and the two can
then disagree. Anything meant to be permanent belongs in <code>.env.example</code>{' '}
as well.
These settings live in the database and survive a restart. A deployment rewrites
<code>.env</code>, not this document, so environment-backed values can then disagree;
permanent environment changes belong in <code>.env.example</code> as well.
</Note>
{data?.settings.updatedBy ? (
<Note>