CH-J ToolsSerververwaltung über SSH
Menü
Veröffentlichte Quellen

CH-J Tools

Durchsuchen Sie Verzeichnisse und Dateien einer bestimmten Anwendungsausgabe.

Quellen als ZIP herunterladen
CH-J / Server Manager 406 Dateien
core-20261024T135205Z-7c487d719de2 Hinzugefügt: · Erstellt:
configStore.js Hinzugefügt: Datum nicht erfasst 5,9 KB · 163 Zeilen
Code Datei herunterladen
1"use strict";
3const fs = require("node:fs");
4const path = require("node:path");
5const { atomicWriteJson } = require("../storage/atomicFile");
7const SUPPORTED_LANGUAGES = Object.freeze(["cs", "de", "en"]);
8const CATALOG_CHANNELS = Object.freeze(["alpha", "beta", "stable", "all"]);
9// Left navigation: mode (full = icon + text, compact = icons, split = icons in two columns), hidden flag kept
10// separately so the menu button restores the last mode, and the user-resized width of the full mode.
11const SIDEBAR_MODES = Object.freeze(["full", "compact", "split"]);
12const SIDEBAR_WIDTH = Object.freeze({ min: 220, max: 420, default: 248 });
14const DEFAULT_CONFIG = Object.freeze({
15 schemaVersion: 1,
16 ui: {
17 language: "cs",
18 theme: "dark",
19 navigationOrder: [],
20 sidebar: { mode: "full", hidden: false, width: SIDEBAR_WIDTH.default }
21 },
22 updates: {
23 channel: "beta",
24 autoCheck: true,
25 lastLaunchedRelease: null,
26 baseUrls: [
27 "https://sm.ch-j.de/"
28 ]
29 },
30 plugins: {
31 channel: "alpha",
32 display: {}
33 },
34 security: { autoLockMinutes: 0, lockOnBlur: false, requireSystemAuthentication: false }
35});
37function clone(value) {
38 return JSON.parse(JSON.stringify(value));
41function normalizeReleaseMarker(value) {
42 if (!value || typeof value !== "object" || Array.isArray(value)) return null;
43 const id = String(value.id || "").trim();
44 const version = String(value.version || "").trim();
45 const publishedAt = String(value.publishedAt || "").trim();
46 const sha512 = String(value.sha512 || "").trim().toLowerCase();
47 if (!id || !version || Number.isNaN(Date.parse(publishedAt)) || !/^[a-f0-9]{128}$/.test(sha512)) return null;
48 return { id, version, publishedAt, sha512 };
51function normalizeSidebar(input) {
52 const source = input && typeof input === "object" ? input : {};
53 const width = Number(source.width);
54 return {
55 mode: SIDEBAR_MODES.includes(source.mode) ? source.mode : "full",
56 hidden: source.hidden === true,
57 width: Number.isFinite(width) ? Math.round(Math.min(SIDEBAR_WIDTH.max, Math.max(SIDEBAR_WIDTH.min, width))) : SIDEBAR_WIDTH.default
58 };
61function normalizeConfig(input = {}) {
62 const source = input && typeof input === "object" && !Array.isArray(input) ? input : {};
63 const channel = CATALOG_CHANNELS.includes(source.updates?.channel)
64 ? source.updates.channel
65 : DEFAULT_CONFIG.updates.channel;
66 const pluginChannel = CATALOG_CHANNELS.includes(source.plugins?.channel)
67 ? source.plugins.channel
68 : DEFAULT_CONFIG.plugins.channel;
69 const theme = ["system", "dark", "light"].includes(source.ui?.theme)
70 ? source.ui.theme
71 : DEFAULT_CONFIG.ui.theme;
72 const language = SUPPORTED_LANGUAGES.includes(String(source.ui?.language || ""))
73 ? String(source.ui.language)
74 : DEFAULT_CONFIG.ui.language;
76 return {
77 schemaVersion: 1,
78 ui: { language, theme, sidebar: normalizeSidebar(source.ui?.sidebar), navigationOrder: Array.isArray(source.ui?.navigationOrder) ? [...new Set(source.ui.navigationOrder.filter((id) => typeof id === "string" && /^(overview|terminal|profiles|updates|plugins|diagnostics|settings|plugin:[a-z0-9][a-z0-9.-]{2,63})$/.test(id)))].slice(0, 200) : [] },
79 updates: {
80 channel,
81 autoCheck: source.updates?.autoCheck !== false,
82 lastLaunchedRelease: normalizeReleaseMarker(source.updates?.lastLaunchedRelease),
83 baseUrls: clone(DEFAULT_CONFIG.updates.baseUrls)
84 },
85 plugins: { channel: pluginChannel, display: Object.fromEntries(Object.entries(source.plugins?.display && typeof source.plugins.display === "object" && !Array.isArray(source.plugins.display) ? source.plugins.display : {}).filter(([id, mode]) => /^[a-z0-9][a-z0-9.-]{2,63}$/.test(id) && ["hidden", "visible", "navigation"].includes(mode))) },
86 security: {
87 autoLockMinutes: [0, 1, 5, 10, 15, 30, 60].includes(source.security?.autoLockMinutes) ? source.security.autoLockMinutes : 0,
88 lockOnBlur: source.security?.lockOnBlur === true,
89 requireSystemAuthentication: source.security?.requireSystemAuthentication === true
90 }
91 };
94class ConfigStore {
95 constructor(rootDir) {
96 this.rootDir = rootDir;
97 this.filePath = path.join(rootDir, "config", "config.json");
98 this.value = clone(DEFAULT_CONFIG);
99 }
101 load() {
102 try {
103 const raw = fs.readFileSync(this.filePath, "utf8");
104 this.value = normalizeConfig(JSON.parse(raw));
105 } catch (error) {
106 if (error?.code !== "ENOENT") {
107 const backup = `${this.filePath}.invalid-${Date.now()}`;
108 try { fs.renameSync(this.filePath, backup); } catch {}
109 }
110 this.value = clone(DEFAULT_CONFIG);
111 this.save();
112 }
113 return this.get();
114 }
116 get() {
117 return clone(this.value);
118 }
120 update(patch = {}) {
121 const updatePatch = patch.updates && typeof patch.updates === "object" ? patch.updates : {};
122 const next = {
123 ...this.value,
124 ui: { ...this.value.ui, ...(patch.ui || {}), sidebar: { ...this.value.ui.sidebar, ...(patch.ui?.sidebar || {}) } },
125 updates: {
126 ...this.value.updates,
127 channel: updatePatch.channel ?? this.value.updates.channel,
128 autoCheck: updatePatch.autoCheck ?? this.value.updates.autoCheck
129 },
130 plugins: { ...this.value.plugins, ...(patch.plugins || {}) },
131 security: { ...this.value.security, ...(patch.security || {}) }
132 };
133 this.value = normalizeConfig(next);
134 this.save();
135 return this.get();
136 }
138 recordLaunchedRelease(release) {
139 const marker = normalizeReleaseMarker(release);
140 if (!marker) throw new Error("Invalid launched update release metadata.");
141 this.value = normalizeConfig({
142 ...this.value,
143 updates: { ...this.value.updates, lastLaunchedRelease: marker }
144 });
145 this.save();
146 return clone(marker);
147 }
149 save() {
150 atomicWriteJson(this.filePath, this.value);
151 }
154module.exports = {
155 SIDEBAR_MODES,
156 SIDEBAR_WIDTH,
157 ConfigStore,
158 CATALOG_CHANNELS,
159 DEFAULT_CONFIG,
160 SUPPORTED_LANGUAGES,
161 normalizeReleaseMarker,
162 normalizeConfig
163};
SHA-25699e100e7d7459d0e8bad691c217d0ee46acd97e5765448edd3a5ec76feddb9f5

CH-J Proprietary Software License 1.14 · Die Quellen werden unter der CH-J Proprietary Software License 1.14 bereitgestellt. Ihre Verfügbarkeit ändert die Lizenzbedingungen nicht und gewährt keine zusätzlichen Rechte.

SHA-256 des Archivs476ed8aef0f1e96a5acdf223127b6692acdf4492eeaf5d0816b6e2a634fbbda6
Dateien