Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion public/api/widget-data/w-stats
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"cpu":12,"ram":38,"temp":39,"temps":{"0":39},"disks":[{"mount":"/","usedPct":42.8,"totalGb":233},{"mount":"/srv/pool","usedPct":67.5,"totalGb":7452}],"iowait":0.4,"procs":186,"uptime":4823910,"download":412.6,"upload":38.4}
{"cpu":12,"ram":38,"temp":39,"temps":{"0":39},"disks":[{"mount":"/","usedPct":42.8,"totalGb":233},{"mount":"/srv/pool","usedPct":67.5,"totalGb":7452}],"iowait":0.4,"procs":186,"uptime":4823910,"download":412.6,"upload":38.4,"history":{"cpu":[12.0,17.0,20.2,20.7,19.0,16.4,14.6,14.3,15.2,16.0,15.4,12.5,8.1,3.7,1.2,1.6,4.7,8.9,12.6,14.5,14.4,13.6,13.3,14.7,17.4,20.3,21.7,20.3,16.4,11.2,6.7,4.4,4.5,6.4,8.4,9.2,8.5,7.1,6.6,12],"ram":[39.5,38.3,38.7,39.0,39.2,39.5,39.7,41.3,39.9,40.0,40.0,39.9,39.8,39.7,40.9,39.2,38.9,38.6,38.3,37.9,37.6,38.8,37.0,36.7,36.5,36.3,36.1,36.0,37.5,36.0,36.1,36.2,36.4,36.6,36.8,38.6,37.4,37.8,38.1,38]}}
12 changes: 6 additions & 6 deletions public/js/admin-app-form.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { clr, el, focusFirst, initial, inp as inpById, isolate, q as qSel, qa, qi, tgt } from '/js/utils.js?v=fdc0243f';
import { clr, el, focusFirst, initial, inp as inpById, isolate, q as qSel, qa, qi, tgt } from '/js/utils.js?v=0d64dfd3';
import { html, raw, setHtml } from '/js/html.js?v=c71f8903';
import { loadLocalIcons, resolveIcon, iconChain, cdnIconRef, splitIconRef } from '/js/icons.js?v=9c7b5111';
import { state } from '/js/admin-state.js?v=af772a1b';
Expand All @@ -24,13 +24,13 @@ import {
reveal,
setTogDisabled,
swapContent,
} from '/js/admin-shared.js?v=d0ef41ba';
import { createListbox } from '/js/listbox.js?v=11c869c4';
} from '/js/admin-shared.js?v=2b48ff3f';
import { createListbox } from '/js/listbox.js?v=35d8318a';
import { MAX_LABELS } from '/js/badge-logic.js?v=9e6d9d4b';
import { renderColorControl, BADGE_DEFAULT } from '/js/admin-color-control.js?v=cee08df9';
import { badgeErrorAdvice, TONE } from '/js/admin-error.js?v=a9a7e60c';
import { renderColorControl, BADGE_DEFAULT } from '/js/admin-color-control.js?v=3b14e88c';
import { badgeErrorAdvice, TONE } from '/js/admin-error.js?v=b0881957';
import { fluidHoverClear, fluidHoverKb } from '/js/fluid-hover.js?v=cb886e86';
import { iconSvg } from '/js/icon-set.js?v=34af798f';
import { iconSvg } from '/js/icon-set.js?v=628341d4';

export function buildFolderForm(body, item) {
const children = item?.children || [];
Expand Down
6 changes: 3 additions & 3 deletions public/js/admin-auth.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { apiGet, apiPost } from '/js/admin-shared.js?v=d0ef41ba';
import { apiGet, apiPost } from '/js/admin-shared.js?v=2b48ff3f';
import { initI18n, t } from '/js/i18n.js?v=5579776a';
import { loginErrorKey } from '/js/admin-error.js?v=a9a7e60c';
import { loginErrorKey } from '/js/admin-error.js?v=b0881957';
import { pwStrength } from '/js/password-strength.js?v=389e0ed0';
import { el, inp as inpById, qa } from '/js/utils.js?v=fdc0243f';
import { el, inp as inpById, qa } from '/js/utils.js?v=0d64dfd3';
import { blockingScreenFor } from '/js/config-recovery.js?v=dbe542e1';

export async function checkAuth(onLogin) {
Expand Down
6 changes: 3 additions & 3 deletions public/js/admin-color-control.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { PE_SVG, initInlineEdit, toast, reveal } from '/js/admin-shared.js?v=d0ef41ba';
import { PE_SVG, initInlineEdit, toast, reveal } from '/js/admin-shared.js?v=2b48ff3f';
import { t } from '/js/i18n.js?v=5579776a';
import { html, raw, setHtml } from '/js/html.js?v=c71f8903';
import { qa, q } from '/js/utils.js?v=fdc0243f';
import { iconSvg } from '/js/icon-set.js?v=34af798f';
import { qa, q } from '/js/utils.js?v=0d64dfd3';
import { iconSvg } from '/js/icon-set.js?v=628341d4';
import { HUE_NAMES, ROLE_NAMES, TILE_KEYWORDS, pageTheme, tileColor } from '/js/palette.js?v=3fb8ae43';

const CC_SWATCHES = ['#1c1c1e', '#8e8e93', '#f2f2f7', '#ff393c', '#ffcd00', '#35c759', '#0289ff', '#cb30df'];
Expand Down
4 changes: 2 additions & 2 deletions public/js/admin-drag.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

import { applyDrop, canJoinFolder, folderRowZone } from '/js/admin-drag-logic.js?v=6b767e76';
import { state } from '/js/admin-state.js?v=af772a1b';
import { snapshotItems } from '/js/admin-save-logic.js?v=ae65f9c8';
import { qa } from '/js/utils.js?v=fdc0243f';
import { snapshotItems } from '/js/admin-save-logic.js?v=5811620e';
import { qa } from '/js/utils.js?v=0d64dfd3';

/** @type {(before: unknown) => void} */
let _save = () => {};
Expand Down
1 change: 1 addition & 0 deletions public/js/admin-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const BY_CODE = Object.freeze({
'blocked.unresolved': 'adminError.unreachable',
'invalid.duplicate-id': 'adminError.duplicateId',
'invalid.missing-children': 'adminError.missingChildren',
'invalid.bad-children': 'adminError.badChildren',
'invalid.unsafe-link': 'adminError.unsafeLink',
'invalid.dock-full': 'app.dockFull',
'network.refused': 'adminError.refused',
Expand Down
14 changes: 7 additions & 7 deletions public/js/admin-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@
reads and writes them too. */

import { collapsedFolders, filter, state } from '/js/admin-state.js?v=af772a1b';
import { snapshotItems } from '/js/admin-save-logic.js?v=ae65f9c8';
import { snapshotItems } from '/js/admin-save-logic.js?v=5811620e';
import { reorderItems } from '/js/admin-logic.js?v=fc7f0836';
import { initDrag, wireRowDrag } from '/js/admin-drag.js?v=5e9c57c7';
import { paintIcon } from '/js/admin-shared.js?v=d0ef41ba';
import { clr, el, focusFirst, initial, qa, setUserText } from '/js/utils.js?v=fdc0243f';
import { initDrag, wireRowDrag } from '/js/admin-drag.js?v=14a608cd';
import { paintIcon } from '/js/admin-shared.js?v=2b48ff3f';
import { clr, el, focusFirst, initial, qa, setUserText } from '/js/utils.js?v=0d64dfd3';
import { html, raw, setHtml } from '/js/html.js?v=c71f8903';
import { t } from '/js/i18n.js?v=5579776a';
import { sizeLabel } from '/js/admin-widget-form.js?v=ea4e9101';
import { widgetGlyph } from '/js/widget-glyphs.js?v=648cc374';
import { iconSvg } from '/js/icon-set.js?v=34af798f';
import { sizeLabel } from '/js/admin-widget-form.js?v=8bb8feb4';
import { widgetGlyph } from '/js/widget-glyphs.js?v=b383b7c8';
import { iconSvg } from '/js/icon-set.js?v=628341d4';

/** @type {{ openModal: (idx: number|null) => void,
openFolderPicker: (appId: string|null, targetFolderId?: string|null) => void,
Expand Down
53 changes: 53 additions & 0 deletions public/js/admin-save-logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,59 @@ export function afterImport({ local, saved, current, serverItems, newItems }) {
return { items: [...local, ...newItems], saved: [...saved, ...newItems], stale: false };
}

/** @param {unknown} a @param {unknown} b @returns {boolean} equal data, whatever the key order */
function sameData(a, b) {
if (a === b) return true;
if (!a || !b || typeof a !== 'object' || typeof b !== 'object') return false;
if (Array.isArray(a) !== Array.isArray(b)) return false;
const ka = Object.keys(a);
return (
ka.length === Object.keys(b).length &&
ka.every(k => Object.hasOwn(b, k) && sameData(/** @type {any} */ (a)[k], /** @type {any} */ (b)[k]))
);
}

/** The settings a backup restores. The server owns `auth` and drops it from
every write.
@param {unknown} settings @returns {Record<string, unknown> | null} */
function restorableSettings(settings) {
if (!settings || typeof settings !== 'object' || Array.isArray(settings)) return null;
const { auth: _auth, ...rest } = /** @type {Record<string, unknown>} */ (settings);
return rest;
}

/** @param {any} file a backup @param {any} live the config as the server holds it
@returns {boolean} whether restoring the file changes the settings */
export function restoresSettings(file, live) {
const next = restorableSettings(file?.settings);
if (!next) return false;
const now = restorableSettings(live?.settings) || {};
/* The server keeps the stored host list when a write leaves it out. */
const server = /** @type {any} */ (next.server);
const kept = /** @type {any} */ (now.server)?.allowedHosts;
if (server?.allowedHosts === undefined && kept !== undefined) next.server = { ...server, allowedHosts: kept };
return !sameData(next, now);
}

/** The config write that restores a backup over `live`. It carries the file's
schema version so the server migrates what an older release wrote.
@param {any} file @param {any} live
@param {boolean} [keepBackground] the file names an uploaded wallpaper this install does not hold
@returns {any} */
export function restoreBody(file, live, keepBackground = false) {
const settings = restorableSettings(file?.settings);
if (settings && keepBackground) {
if (live?.settings?.background) settings.background = live.settings.background;
else delete settings.background;
}
return {
_rev: live?._rev,
_schemaVersion: file?._schemaVersion,
items: file?.items,
settings: settings || live?.settings,
};
}

/** Run writes one at a time, in the order asked. A write asked for while
another runs waits for it instead of being dropped.

Expand Down
12 changes: 6 additions & 6 deletions public/js/admin-settings.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { toast, apiGet, apiPost, errorText, reveal, swapContent } from '/js/admin-shared.js?v=d0ef41ba';
import { socketProbeAdvice } from '/js/admin-error.js?v=a9a7e60c';
import { toast, apiGet, apiPost, errorText, reveal, swapContent } from '/js/admin-shared.js?v=2b48ff3f';
import { socketProbeAdvice } from '/js/admin-error.js?v=b0881957';
import { pwStrength } from '/js/password-strength.js?v=389e0ed0';
import { t } from '/js/i18n.js?v=5579776a';
import {
Expand All @@ -11,11 +11,11 @@ import {
BLOCK,
} from '/js/admin-logic.js?v=fc7f0836';
import { confirmText, promptModal } from '/js/modal.js?v=6b0320bd';
import { el, inp, setUserText } from '/js/utils.js?v=fdc0243f';
import { el, inp, setUserText } from '/js/utils.js?v=0d64dfd3';
import { formatNumber } from '/js/format-number.js?v=349a741d';
import { serialWrites } from '/js/admin-save-logic.js?v=ae65f9c8';
import { renderColorControl } from '/js/admin-color-control.js?v=cee08df9';
import { BACKDROP } from '/js/background.js?v=abd33088';
import { serialWrites } from '/js/admin-save-logic.js?v=5811620e';
import { renderColorControl } from '/js/admin-color-control.js?v=3b14e88c';
import { BACKDROP } from '/js/background.js?v=fe705356';
import {
ALLOWED_HOSTS_MAX,
firstBadHost,
Expand Down
31 changes: 22 additions & 9 deletions public/js/admin-shared.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
/* Stateless helpers shared by the admin modules. Mutable state stays out. */
import { recoversSession, toastHoldMs } from '/js/admin-logic.js?v=fc7f0836';
import { el, isolate, q } from '/js/utils.js?v=fdc0243f';
import { el, isolate, q } from '/js/utils.js?v=0d64dfd3';
import { t } from '/js/i18n.js?v=5579776a';
import { errorAdvice } from '/js/admin-error.js?v=a9a7e60c';
import { errorAdvice } from '/js/admin-error.js?v=b0881957';
import { formatNumber } from '/js/format-number.js?v=349a741d';
import { iconChain } from '/js/icons.js?v=9c7b5111';
import { iconSvg } from '/js/icon-set.js?v=34af798f';
import { iconSvg } from '/js/icon-set.js?v=628341d4';
import { blockingScreenFor, recoveryShown, showBlockingScreen } from '/js/config-recovery.js?v=dbe542e1';

export const API = '';

let tt;
let _toastWired = false;
let _pinned = false;

/** @param {string} m @param {'ok'|'err'} [tone] @returns {void} */
export const toast = (m, tone = 'ok') => {
/** @param {string} m @param {'ok'|'err'} [tone]
@param {{ pin?: boolean }} [opts] pin: no 'ok' notice replaces it until it is dismissed
@returns {void} */
export const toast = (m, tone = 'ok', { pin = false } = {}) => {
const e = el('toast');
if (!e) return;
if (tone === 'ok' && _pinned && e.classList.contains('show')) return;
_pinned = pin;
e.textContent = m;
e.className = `show ${tone}`;
clearTimeout(tt);
Expand All @@ -35,6 +40,7 @@ export const toast = (m, tone = 'ok') => {
e.addEventListener('click', () => {
clearTimeout(tt);
e.className = '';
_pinned = false;
});
}
const ms = toastHoldMs(tone, m, 'show');
Expand Down Expand Up @@ -135,13 +141,20 @@ export const apiPost = async (p, b, recover = true) => {
const body = await r.json();
/* Every config write goes through here, and a withheld credential has to be
said out loud. */
const withheld = (body?.withheld || [])
const withheld = withheldText(body);
if (withheld) toast(withheld, 'err', { pin: true });
return body;
};

/** @param {any} body a config write's reply
@returns {string} the notice naming the credentials it withheld, or '' */
export function withheldText(body) {
const names = (body?.withheld || [])
.map(w => w.label)
.filter(Boolean)
.map(isolate);
if (withheld.length) toast(t('toast.secretsWithheld', { items: withheld.join(', ') }), 'err');
return body;
};
return names.length ? t('toast.secretsWithheld', { items: names.join(', ') }) : '';
}

/* A native `disabled` control is skipped by screen readers. aria-disabled keeps
it announced but carries no behaviour, so activation is blocked here. */
Expand Down
10 changes: 5 additions & 5 deletions public/js/admin-widget-form.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { state } from '/js/admin-state.js?v=af772a1b';
import { PE_SVG, initInlineEdit, swapContent } from '/js/admin-shared.js?v=d0ef41ba';
import { createListbox } from '/js/listbox.js?v=11c869c4';
import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=effb84f9';
import { PE_SVG, initInlineEdit, swapContent } from '/js/admin-shared.js?v=2b48ff3f';
import { createListbox } from '/js/listbox.js?v=35d8318a';
import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=02ffdaba';
import { html, raw, setHtml } from '/js/html.js?v=c71f8903';
import { sizesForView, widgetConfigMode, rejectionLines, carriesTypedValues } from '/js/admin-logic.js?v=fc7f0836';
import { t } from '/js/i18n.js?v=5579776a';
import { focusFirst, q, qi, qa } from '/js/utils.js?v=fdc0243f';
import { iconSvg } from '/js/icon-set.js?v=34af798f';
import { focusFirst, q, qi, qa } from '/js/utils.js?v=0d64dfd3';
import { iconSvg } from '/js/icon-set.js?v=628341d4';

const SIZES_WITH_ICONS = new Set(['small', 'medium', 'large', 'xlarge']);

Expand Down
Loading
Loading