feat(settings): enhance settings management by merging config and devices data from WebDAV
This commit is contained in:
parent
941bbbc12a
commit
4fe0adccc6
|
|
@ -18,8 +18,9 @@ import {
|
||||||
putFileContents,
|
putFileContents,
|
||||||
} from './webdav';
|
} from './webdav';
|
||||||
|
|
||||||
/** The canonical path on the WebDAV server. */
|
/** The canonical paths on the WebDAV server. */
|
||||||
export const SETTINGS_PATH = '/.sys/config.json';
|
export const SETTINGS_PATH = '/.sys/config.json';
|
||||||
|
export const DEVICES_PATH = '/.sys/devices.json';
|
||||||
const SETTINGS_DIR = '/.sys';
|
const SETTINGS_DIR = '/.sys';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -37,15 +38,32 @@ const SAVED_INDICATOR_MS = 1500;
|
||||||
|
|
||||||
export type SettingsConfig = Record<string, any>;
|
export type SettingsConfig = Record<string, any>;
|
||||||
|
|
||||||
/** Read the settings file from the WebDAV server. Returns null on failure. */
|
/** Read both config files from the WebDAV server and merge them. Returns null on failure. */
|
||||||
export async function readSettings(): Promise<SettingsConfig | null> {
|
export async function readSettings(): Promise<SettingsConfig | null> {
|
||||||
try {
|
try {
|
||||||
const blob = await getFileContents(SETTINGS_PATH);
|
const [configText, devicesText] = await Promise.all([
|
||||||
const text = await blob.text();
|
getFileContents(SETTINGS_PATH).then(b => b.text()),
|
||||||
if (!text) return null;
|
getFileContents(DEVICES_PATH).then(b => b.text()).catch(() => null),
|
||||||
const parsed = JSON.parse(text);
|
]);
|
||||||
if (parsed && typeof parsed === 'object') return parsed as SettingsConfig;
|
if (!configText) return null;
|
||||||
return null;
|
const config = JSON.parse(configText);
|
||||||
|
if (!config || typeof config !== 'object') return null;
|
||||||
|
if (devicesText) {
|
||||||
|
const devices = JSON.parse(devicesText);
|
||||||
|
if (devices && typeof devices === 'object') {
|
||||||
|
// One-level deep merge: devices.json keys are merged into matching
|
||||||
|
// top-level objects in config (e.g. devices.iec.devices → config.iec.devices).
|
||||||
|
for (const [k, v] of Object.entries(devices)) {
|
||||||
|
if (config[k] && typeof config[k] === 'object' && !Array.isArray(config[k]) &&
|
||||||
|
v && typeof v === 'object' && !Array.isArray(v)) {
|
||||||
|
config[k] = { ...config[k], ...(v as object) };
|
||||||
|
} else {
|
||||||
|
config[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config as SettingsConfig;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -62,8 +80,12 @@ export async function writeSettings(config: SettingsConfig): Promise<void> {
|
||||||
} catch {
|
} catch {
|
||||||
/* directory may already exist; ignore */
|
/* directory may already exist; ignore */
|
||||||
}
|
}
|
||||||
const json = JSON.stringify(config, null, 2) + '\n';
|
const { iec, ...mainConfig } = config;
|
||||||
await putFileContents(SETTINGS_PATH, json);
|
const { devices: iecDevices, ...iecBusConfig } = (iec ?? {}) as any;
|
||||||
|
await Promise.all([
|
||||||
|
putFileContents(SETTINGS_PATH, JSON.stringify({ ...mainConfig, iec: iecBusConfig }, null, 2) + '\n'),
|
||||||
|
putFileContents(DEVICES_PATH, JSON.stringify({ iec: { devices: iecDevices } }, null, 2) + '\n'),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SaveStatus =
|
export type SaveStatus =
|
||||||
|
|
@ -176,9 +198,17 @@ export function useSettings(): UseSettingsResult {
|
||||||
// Modern browsers: `fetch` with `keepalive: true` continues even
|
// Modern browsers: `fetch` with `keepalive: true` continues even
|
||||||
// after the page is being unloaded. We don't await it.
|
// after the page is being unloaded. We don't await it.
|
||||||
try {
|
try {
|
||||||
void fetch(absoluteSettingsUrl(), {
|
const { iec, ...mainConfig } = configRef.current;
|
||||||
|
const { devices: iecDevices, ...iecBusConfig } = (iec ?? {}) as any;
|
||||||
|
void fetch(absoluteUrl(SETTINGS_PATH), {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify(configRef.current, null, 2) + '\n',
|
body: JSON.stringify({ ...mainConfig, iec: iecBusConfig }, null, 2) + '\n',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
keepalive: true,
|
||||||
|
});
|
||||||
|
void fetch(absoluteUrl(DEVICES_PATH), {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify({ iec: { devices: iecDevices } }, null, 2) + '\n',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
keepalive: true,
|
keepalive: true,
|
||||||
});
|
});
|
||||||
|
|
@ -226,8 +256,8 @@ export function useSettings(): UseSettingsResult {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function absoluteSettingsUrl(): string {
|
function absoluteUrl(path: string): string {
|
||||||
return `${window.location.protocol}//${window.location.hostname}${
|
return `${window.location.protocol}//${window.location.hostname}${
|
||||||
window.location.port ? ':' + window.location.port : ''
|
window.location.port ? ':' + window.location.port : ''
|
||||||
}${SETTINGS_PATH}`;
|
}${path}`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user