claw-dashboard 1.9.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.c8rc.json +38 -0
- package/.dockerignore +68 -0
- package/.github/dependabot.yml +45 -0
- package/.github/pull_request_template.md +39 -0
- package/.github/workflows/ci.yml +147 -0
- package/.github/workflows/release.yml +40 -0
- package/.github/workflows/security.yml +84 -0
- package/.husky/pre-commit +1 -0
- package/.planning/codebase/ARCHITECTURE.md +206 -0
- package/.planning/codebase/INTEGRATIONS.md +150 -0
- package/.planning/codebase/STACK.md +122 -0
- package/.planning/codebase/STRUCTURE.md +201 -0
- package/CHANGELOG.md +293 -0
- package/CONTRIBUTING.md +378 -0
- package/Dockerfile +54 -0
- package/FEATURES.md +195 -21
- package/README.md +83 -6
- package/TODO.md +28 -0
- package/build-cjs.js +127 -0
- package/cjs-shim.js +13 -0
- package/dist/clawdash +1729 -0
- package/dist/clawdash.meta.json +2236 -0
- package/dist/widgets.cjs +6511 -0
- package/docker-compose.yml +67 -0
- package/docs/API.md +1050 -0
- package/docs/PLUGINS.md +1504 -0
- package/esbuild.config.js +158 -0
- package/eslint.config.js +56 -0
- package/examples/plugins/README.md +122 -0
- package/examples/plugins/api-status/index.js +294 -0
- package/examples/plugins/api-status/plugin.json +19 -0
- package/examples/plugins/hello-world/index.js +104 -0
- package/examples/plugins/hello-world/plugin.json +15 -0
- package/examples/plugins/system-metrics-chart/index.js +339 -0
- package/examples/plugins/system-metrics-chart/plugin.json +18 -0
- package/examples/plugins/weather-widget/index.js +136 -0
- package/examples/plugins/weather-widget/plugin.json +19 -0
- package/index.cjs +23005 -0
- package/index.js +5236 -566
- package/jest.config.js +11 -0
- package/man/clawdash.1 +276 -0
- package/package.json +54 -5
- package/schemas/plugin-manifest.json +128 -0
- package/scripts/release.js +595 -0
- package/src/alerts.js +693 -0
- package/src/auto-save.js +584 -0
- package/src/cache.js +390 -0
- package/src/checksum.js +146 -0
- package/src/cli/args.js +110 -0
- package/src/cli/export-schedule.js +423 -0
- package/src/cli/export-snapshot.js +138 -0
- package/src/cli/help.js +69 -0
- package/src/cli/import-snapshot.js +230 -0
- package/src/cli/index.js +14 -0
- package/src/cli/list-templates.js +69 -0
- package/src/cli/validate-config.js +76 -0
- package/src/cli/validate-plugin.js +187 -0
- package/src/cli/version.js +15 -0
- package/src/config-validator.js +586 -0
- package/src/config-watcher.js +401 -0
- package/src/config.js +684 -0
- package/src/container-detector.js +499 -0
- package/src/database.js +734 -0
- package/src/differential-render.js +327 -0
- package/src/errors.js +169 -0
- package/src/export-scheduler.js +581 -0
- package/src/gateway-manager.js +685 -0
- package/src/hints.js +371 -0
- package/src/loading-states.js +509 -0
- package/src/logger.js +185 -0
- package/src/memory-pressure.js +467 -0
- package/src/performance-monitor.js +374 -0
- package/src/plugin-errors.js +586 -0
- package/src/plugin-manifest-validator.js +219 -0
- package/src/plugin-reload.js +481 -0
- package/src/plugin-scaffold.js +1941 -0
- package/src/plugin-validator.js +284 -0
- package/src/retry.js +218 -0
- package/src/security.js +860 -0
- package/src/snapshot.js +372 -0
- package/src/splash.js +115 -0
- package/src/theme-selector.js +411 -0
- package/src/themes.js +874 -0
- package/src/transitions.js +534 -0
- package/src/types.d.ts +174 -0
- package/src/validation.js +926 -0
- package/src/web-server.js +885 -0
- package/src/widgets/builtin-widgets.js +1057 -0
- package/src/widgets/config-processor.js +401 -0
- package/src/widgets/dependency-resolver.js +596 -0
- package/src/widgets/index.js +79 -0
- package/src/widgets/plugin-api.js +845 -0
- package/src/widgets/widget-error-boundary.js +773 -0
- package/src/widgets/widget-error-isolation.js +551 -0
- package/src/widgets/widget-loader.js +1437 -0
- package/src/workers/system-worker.js +130 -0
- package/src/workers/worker-pool.js +633 -0
- package/tests/alerts.test.js +437 -0
- package/tests/auto-save.test.js +529 -0
- package/tests/cache.test.js +317 -0
- package/tests/cli.test.js +351 -0
- package/tests/command-palette.test.js +320 -0
- package/tests/config-processor.test.js +452 -0
- package/tests/config-validator.test.js +710 -0
- package/tests/config-watcher.test.js +594 -0
- package/tests/config.test.js +755 -0
- package/tests/database.test.js +438 -0
- package/tests/errors.test.js +189 -0
- package/tests/example-plugins.test.js +624 -0
- package/tests/integration.test.js +1321 -0
- package/tests/loading-states.test.js +300 -0
- package/tests/manifest-validation-on-load.test.js +671 -0
- package/tests/performance-monitor.test.js +190 -0
- package/tests/plugin-api-rate-limit.test.js +302 -0
- package/tests/plugin-errors.test.js +311 -0
- package/tests/plugin-lifecycle-e2e.test.js +1036 -0
- package/tests/plugin-reload.test.js +764 -0
- package/tests/rate-limiter.test.js +539 -0
- package/tests/retry.test.js +308 -0
- package/tests/security.test.js +411 -0
- package/tests/settings-modal.test.js +226 -0
- package/tests/theme-selector.test.js +57 -0
- package/tests/utils.js +242 -0
- package/tests/utils.test.js +317 -0
- package/tests/validate-plugin-cli.test.js +359 -0
- package/tests/validation.test.js +837 -0
- package/tests/web-server.test.js +646 -0
- package/tests/widget-arrange-mode.test.js +183 -0
- package/tests/widget-config-hot-reload.test.js +465 -0
- package/tests/widget-dependency.test.js +591 -0
- package/tests/widget-error-boundary.test.js +749 -0
- package/tests/widget-error-isolation.test.js +469 -0
- package/tests/widget-integration.test.js +1147 -0
- package/tests/widget-refresh-intervals.test.js +284 -0
- package/tests/worker-pool.test.js +522 -0
|
@@ -0,0 +1,926 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input validation module for settings and configuration values
|
|
3
|
+
* Validates all user inputs for settings like exportDirectory, theme, etc.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import logger from './logger.js';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import config, { EXPORT_SCHEDULE } from './config.js';
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import { resolve, dirname } from 'path';
|
|
11
|
+
import { CronParser } from './export-scheduler.js';
|
|
12
|
+
|
|
13
|
+
// Valid option values
|
|
14
|
+
const VALID_THEMES = config.VALIDATION.VALID_THEMES;
|
|
15
|
+
const VALID_SORT_MODES = config.VALIDATION.VALID_SORT_MODES;
|
|
16
|
+
const VALID_LOG_LEVELS = config.VALIDATION.VALID_LOG_LEVELS;
|
|
17
|
+
const VALID_EXPORT_FORMATS = config.VALIDATION.VALID_EXPORT_FORMATS;
|
|
18
|
+
|
|
19
|
+
// Validation constraints
|
|
20
|
+
const CONSTRAINTS = {
|
|
21
|
+
refreshInterval: {
|
|
22
|
+
min: config.VALIDATION.REFRESH_INTERVAL.MIN,
|
|
23
|
+
max: config.VALIDATION.REFRESH_INTERVAL.MAX,
|
|
24
|
+
type: 'number'
|
|
25
|
+
},
|
|
26
|
+
logLevelFilter: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
values: VALID_LOG_LEVELS
|
|
29
|
+
},
|
|
30
|
+
sessionSortMode: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
values: VALID_SORT_MODES
|
|
33
|
+
},
|
|
34
|
+
theme: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
values: VALID_THEMES
|
|
37
|
+
},
|
|
38
|
+
exportFormat: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
values: VALID_EXPORT_FORMATS
|
|
41
|
+
},
|
|
42
|
+
exportDirectory: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
required: false
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validate a file path
|
|
50
|
+
* @param {string} filePath - Path to validate
|
|
51
|
+
* @param {boolean} mustExist - Whether the path must exist
|
|
52
|
+
* @returns {object} Validation result
|
|
53
|
+
*/
|
|
54
|
+
function validatePath(filePath, mustExist = false) {
|
|
55
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
56
|
+
return { valid: false, error: 'Path must be a non-empty string' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check for path traversal
|
|
60
|
+
if (filePath.includes('..')) {
|
|
61
|
+
return { valid: false, error: 'Path traversal not allowed' };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Expand tilde
|
|
65
|
+
const expandedPath = filePath.startsWith('~')
|
|
66
|
+
? resolve(os.homedir(), filePath.slice(1))
|
|
67
|
+
: resolve(filePath);
|
|
68
|
+
|
|
69
|
+
// Check if exists if required
|
|
70
|
+
if (mustExist && !fs.existsSync(expandedPath)) {
|
|
71
|
+
return { valid: false, error: `Path does not exist: ${expandedPath}` };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check if path is writable (directory exists or parent exists)
|
|
75
|
+
const parentDir = dirname(expandedPath);
|
|
76
|
+
if (!fs.existsSync(parentDir) && !fs.existsSync(expandedPath)) {
|
|
77
|
+
// Allow if parent can be created
|
|
78
|
+
try {
|
|
79
|
+
const parentExists = fs.existsSync(parentDir);
|
|
80
|
+
if (!parentExists) {
|
|
81
|
+
return { valid: true, resolvedPath: expandedPath, warning: 'Parent directory will be created' };
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
return { valid: false, error: 'Cannot determine if path is writable' };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { valid: true, resolvedPath: expandedPath };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validate refresh interval
|
|
93
|
+
* @param {any} value - Value to validate
|
|
94
|
+
* @returns {object} Validation result
|
|
95
|
+
*/
|
|
96
|
+
function validateRefreshInterval(value) {
|
|
97
|
+
if (value === undefined || value === null) {
|
|
98
|
+
return { valid: true, value: config.REFRESH_INTERVALS.DEFAULT }; // Default
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const num = Number(value);
|
|
102
|
+
|
|
103
|
+
if (isNaN(num)) {
|
|
104
|
+
return { valid: false, error: 'Refresh interval must be a number' };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (num < CONSTRAINTS.refreshInterval.min || num > CONSTRAINTS.refreshInterval.max) {
|
|
108
|
+
return {
|
|
109
|
+
valid: false,
|
|
110
|
+
error: `Refresh interval must be between ${CONSTRAINTS.refreshInterval.min}ms and ${CONSTRAINTS.refreshInterval.max}ms`
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { valid: true, value: num };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Validate log level filter
|
|
119
|
+
* @param {any} value - Value to validate
|
|
120
|
+
* @returns {object} Validation result
|
|
121
|
+
*/
|
|
122
|
+
function validateLogLevelFilter(value) {
|
|
123
|
+
if (!value) {
|
|
124
|
+
return { valid: true, value: 'all' }; // Default
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (typeof value !== 'string') {
|
|
128
|
+
return { valid: false, error: 'Log level must be a string' };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const normalized = value.toLowerCase();
|
|
132
|
+
|
|
133
|
+
if (!CONSTRAINTS.logLevelFilter.values.includes(normalized)) {
|
|
134
|
+
return {
|
|
135
|
+
valid: false,
|
|
136
|
+
error: `Invalid log level. Must be one of: ${CONSTRAINTS.logLevelFilter.values.join(', ')}`
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { valid: true, value: normalized };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Validate session sort mode
|
|
145
|
+
* @param {any} value - Value to validate
|
|
146
|
+
* @returns {object} Validation result
|
|
147
|
+
*/
|
|
148
|
+
function validateSessionSortMode(value) {
|
|
149
|
+
if (!value) {
|
|
150
|
+
return { valid: true, value: 'time' }; // Default
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (typeof value !== 'string') {
|
|
154
|
+
return { valid: false, error: 'Sort mode must be a string' };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const normalized = value.toLowerCase();
|
|
158
|
+
|
|
159
|
+
if (!CONSTRAINTS.sessionSortMode.values.includes(normalized)) {
|
|
160
|
+
return {
|
|
161
|
+
valid: false,
|
|
162
|
+
error: `Invalid sort mode. Must be one of: ${CONSTRAINTS.sessionSortMode.values.join(', ')}`
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { valid: true, value: normalized };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Validate theme
|
|
171
|
+
* @param {any} value - Value to validate
|
|
172
|
+
* @returns {object} Validation result
|
|
173
|
+
*/
|
|
174
|
+
function validateTheme(value) {
|
|
175
|
+
if (!value) {
|
|
176
|
+
return { valid: true, value: 'default' }; // Default
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (typeof value !== 'string') {
|
|
180
|
+
return { valid: false, error: 'Theme must be a string' };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const normalized = value.toLowerCase();
|
|
184
|
+
|
|
185
|
+
if (!CONSTRAINTS.theme.values.includes(normalized)) {
|
|
186
|
+
return {
|
|
187
|
+
valid: false,
|
|
188
|
+
error: `Invalid theme. Must be one of: ${CONSTRAINTS.theme.values.join(', ')}`
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { valid: true, value: normalized };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Validate export format
|
|
197
|
+
* @param {any} value - Value to validate
|
|
198
|
+
* @returns {object} Validation result
|
|
199
|
+
*/
|
|
200
|
+
function validateExportFormat(value) {
|
|
201
|
+
if (!value) {
|
|
202
|
+
return { valid: true, value: 'json' }; // Default
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (typeof value !== 'string') {
|
|
206
|
+
return { valid: false, error: 'Export format must be a string' };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const normalized = value.toLowerCase();
|
|
210
|
+
|
|
211
|
+
if (!CONSTRAINTS.exportFormat.values.includes(normalized)) {
|
|
212
|
+
return {
|
|
213
|
+
valid: false,
|
|
214
|
+
error: `Invalid export format. Must be one of: ${CONSTRAINTS.exportFormat.values.join(', ')}`
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { valid: true, value: normalized };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Validate export directory
|
|
223
|
+
* @param {any} value - Value to validate
|
|
224
|
+
* @returns {object} Validation result
|
|
225
|
+
*/
|
|
226
|
+
function validateExportDirectory(value) {
|
|
227
|
+
if (!value) {
|
|
228
|
+
// Default directory
|
|
229
|
+
return { valid: true, value: config.PATHS.EXPORTS };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (typeof value !== 'string') {
|
|
233
|
+
return { valid: false, error: 'Export directory must be a string' };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return validatePath(value, false);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Validate a boolean setting
|
|
241
|
+
* @param {any} value - Value to validate
|
|
242
|
+
* @param {string} name - Name of the setting for error messages
|
|
243
|
+
* @returns {object} Validation result
|
|
244
|
+
*/
|
|
245
|
+
function validateBoolean(value, name = 'setting') {
|
|
246
|
+
if (value === undefined || value === null) {
|
|
247
|
+
return { valid: true, value: true }; // Default
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (typeof value !== 'boolean') {
|
|
251
|
+
// Try to parse string booleans
|
|
252
|
+
if (value === 'true' || value === '1' || value === 'yes') {
|
|
253
|
+
return { valid: true, value: true };
|
|
254
|
+
}
|
|
255
|
+
if (value === 'false' || value === '0' || value === 'no') {
|
|
256
|
+
return { valid: true, value: false };
|
|
257
|
+
}
|
|
258
|
+
return { valid: false, error: `${name} must be a boolean` };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return { valid: true, value: Boolean(value) };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Validate widget visibility (showWidget1-7)
|
|
266
|
+
* @param {any} value - Value to validate
|
|
267
|
+
* @returns {object} Validation result
|
|
268
|
+
*/
|
|
269
|
+
function validateWidgetVisibility(value) {
|
|
270
|
+
return validateBoolean(value, 'Widget visibility');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Validate pinned widgets array
|
|
275
|
+
* @param {any} value - Value to validate (array of widget IDs)
|
|
276
|
+
* @returns {object} Validation result
|
|
277
|
+
*/
|
|
278
|
+
function validatePinnedWidgets(value) {
|
|
279
|
+
if (!value) {
|
|
280
|
+
return { valid: true, value: [] };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!Array.isArray(value)) {
|
|
284
|
+
return { valid: false, error: 'pinnedWidgets must be an array' };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Valid widget IDs that can be pinned
|
|
288
|
+
const validWidgetIds = ['cpu', 'mem', 'gpu', 'net', 'disk', 'sys', 'uptime', 'health', 'gateway'];
|
|
289
|
+
|
|
290
|
+
// Validate each widget ID
|
|
291
|
+
const validated = [];
|
|
292
|
+
for (const widgetId of value) {
|
|
293
|
+
if (typeof widgetId === 'string' && validWidgetIds.includes(widgetId)) {
|
|
294
|
+
validated.push(widgetId);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Limit to max 4 pinned widgets
|
|
299
|
+
if (validated.length > 4) {
|
|
300
|
+
return { valid: true, value: validated.slice(0, 4), warning: 'Maximum 4 widgets can be pinned, truncating to first 4' };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return { valid: true, value: validated };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Validate widget order for drag-and-drop arrangement
|
|
308
|
+
* @param {any} value - Widget order array to validate
|
|
309
|
+
* @returns {object} Validation result
|
|
310
|
+
*/
|
|
311
|
+
function validateWidgetOrder(value) {
|
|
312
|
+
if (!value) {
|
|
313
|
+
return { valid: true, value: [] };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (!Array.isArray(value)) {
|
|
317
|
+
return { valid: false, error: 'widgetOrder must be an array' };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Valid widget IDs
|
|
321
|
+
const validWidgetIds = ['cpu', 'mem', 'gpu', 'net', 'disk', 'sys', 'uptime', 'health', 'gateway'];
|
|
322
|
+
|
|
323
|
+
// Validate each widget ID and remove duplicates (keep first occurrence)
|
|
324
|
+
const validated = [];
|
|
325
|
+
const seen = new Set();
|
|
326
|
+
for (const widgetId of value) {
|
|
327
|
+
if (typeof widgetId === 'string' && validWidgetIds.includes(widgetId) && !seen.has(widgetId)) {
|
|
328
|
+
validated.push(widgetId);
|
|
329
|
+
seen.add(widgetId);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return { valid: true, value: validated };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Validate widget sizes configuration
|
|
338
|
+
* @param {any} widgetSizes - Widget sizes object to validate
|
|
339
|
+
* @returns {object} Validation result
|
|
340
|
+
*/
|
|
341
|
+
function validateWidgetSizes(widgetSizes) {
|
|
342
|
+
if (!widgetSizes || typeof widgetSizes !== 'object') {
|
|
343
|
+
return { valid: true, value: {} };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const validSizes = ['small', 'medium', 'large', 'wide'];
|
|
347
|
+
const validWidgetIds = ['cpu', 'mem', 'gpu', 'net', 'disk', 'sys', 'uptime', 'health', 'gateway'];
|
|
348
|
+
const result = { valid: true, value: {} };
|
|
349
|
+
|
|
350
|
+
for (const [widgetId, size] of Object.entries(widgetSizes)) {
|
|
351
|
+
if (validWidgetIds.includes(widgetId)) {
|
|
352
|
+
if (validSizes.includes(size)) {
|
|
353
|
+
result.value[widgetId] = size;
|
|
354
|
+
}
|
|
355
|
+
// Invalid size is ignored (will use default)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Validate alert thresholds
|
|
364
|
+
* @param {any} thresholds - Thresholds object to validate
|
|
365
|
+
* @returns {object} Validation result
|
|
366
|
+
*/
|
|
367
|
+
function validateAlertThresholds(thresholds) {
|
|
368
|
+
if (!thresholds || typeof thresholds !== 'object') {
|
|
369
|
+
return { valid: false, error: 'Alert thresholds must be an object' };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const result = { valid: true, value: {} };
|
|
373
|
+
const allowedTypes = ['cpu', 'memory', 'disk'];
|
|
374
|
+
|
|
375
|
+
for (const type of allowedTypes) {
|
|
376
|
+
if (thresholds[type]) {
|
|
377
|
+
const t = thresholds[type];
|
|
378
|
+
|
|
379
|
+
if (typeof t !== 'object') {
|
|
380
|
+
return { valid: false, error: `Alert threshold for ${type} must be an object` };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
result.value[type] = {};
|
|
384
|
+
|
|
385
|
+
// Validate warning threshold
|
|
386
|
+
if (t.warning !== undefined) {
|
|
387
|
+
const warning = Number(t.warning);
|
|
388
|
+
if (isNaN(warning) || warning < 0 || warning > 100) {
|
|
389
|
+
return { valid: false, error: `${type} warning threshold must be 0-100` };
|
|
390
|
+
}
|
|
391
|
+
result.value[type].warning = warning;
|
|
392
|
+
} else {
|
|
393
|
+
result.value[type].warning = type === 'disk' ? 80 : 70; // Defaults
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Validate critical threshold
|
|
397
|
+
if (t.critical !== undefined) {
|
|
398
|
+
const critical = Number(t.critical);
|
|
399
|
+
if (isNaN(critical) || critical < 0 || critical > 100) {
|
|
400
|
+
return { valid: false, error: `${type} critical threshold must be 0-100` };
|
|
401
|
+
}
|
|
402
|
+
result.value[type].critical = critical;
|
|
403
|
+
} else {
|
|
404
|
+
result.value[type].critical = type === 'disk' ? 95 : 90; // Defaults
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Ensure critical >= warning
|
|
408
|
+
if (result.value[type].critical < result.value[type].warning) {
|
|
409
|
+
return { valid: false, error: `${type} critical threshold must be >= warning threshold` };
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return result;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Validate auto-retry configuration
|
|
419
|
+
* @param {object} autoRetry - Auto-retry configuration to validate
|
|
420
|
+
* @returns {object} Validation result
|
|
421
|
+
*/
|
|
422
|
+
function validateAutoRetry(autoRetry) {
|
|
423
|
+
if (!autoRetry || typeof autoRetry !== 'object') {
|
|
424
|
+
// Return defaults if not provided
|
|
425
|
+
return {
|
|
426
|
+
valid: true,
|
|
427
|
+
value: {
|
|
428
|
+
enabled: config.AUTO_RETRY.ENABLED,
|
|
429
|
+
intervalMs: config.AUTO_RETRY.DEFAULT_INTERVAL_MS,
|
|
430
|
+
exponentialBackoff: config.AUTO_RETRY.EXPONENTIAL_BACKOFF,
|
|
431
|
+
backoffMultiplier: config.AUTO_RETRY.BACKOFF_MULTIPLIER,
|
|
432
|
+
maxBackoffIntervalMs: config.AUTO_RETRY.MAX_BACKOFF_INTERVAL_MS,
|
|
433
|
+
resetAfterSuccess: config.AUTO_RETRY.RESET_AFTER_SUCCESS,
|
|
434
|
+
consecutiveFailureThreshold: config.AUTO_RETRY.CONSECUTIVE_FAILURE_THRESHOLD,
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const validated = {};
|
|
440
|
+
const constraints = config.VALIDATION.AUTO_RETRY;
|
|
441
|
+
|
|
442
|
+
// Validate enabled (default: true)
|
|
443
|
+
validated.enabled = autoRetry.enabled !== false;
|
|
444
|
+
|
|
445
|
+
// Validate intervalMs
|
|
446
|
+
const interval = Number(autoRetry.intervalMs);
|
|
447
|
+
if (autoRetry.intervalMs !== undefined && (!isNaN(interval) && interval >= constraints.INTERVAL_MS.MIN && interval <= constraints.INTERVAL_MS.MAX)) {
|
|
448
|
+
validated.intervalMs = interval;
|
|
449
|
+
} else {
|
|
450
|
+
validated.intervalMs = config.AUTO_RETRY.DEFAULT_INTERVAL_MS;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Validate exponentialBackoff (default: true)
|
|
454
|
+
validated.exponentialBackoff = autoRetry.exponentialBackoff !== false;
|
|
455
|
+
|
|
456
|
+
// Validate backoffMultiplier
|
|
457
|
+
const multiplier = Number(autoRetry.backoffMultiplier);
|
|
458
|
+
if (autoRetry.backoffMultiplier !== undefined && (!isNaN(multiplier) && multiplier >= constraints.BACKOFF_MULTIPLIER.MIN && multiplier <= constraints.BACKOFF_MULTIPLIER.MAX)) {
|
|
459
|
+
validated.backoffMultiplier = multiplier;
|
|
460
|
+
} else {
|
|
461
|
+
validated.backoffMultiplier = config.AUTO_RETRY.BACKOFF_MULTIPLIER;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Validate maxBackoffIntervalMs
|
|
465
|
+
const maxBackoff = Number(autoRetry.maxBackoffIntervalMs);
|
|
466
|
+
if (autoRetry.maxBackoffIntervalMs !== undefined && (!isNaN(maxBackoff) && maxBackoff >= constraints.MAX_BACKOFF_INTERVAL_MS.MIN && maxBackoff <= constraints.MAX_BACKOFF_INTERVAL_MS.MAX)) {
|
|
467
|
+
validated.maxBackoffIntervalMs = maxBackoff;
|
|
468
|
+
} else {
|
|
469
|
+
validated.maxBackoffIntervalMs = config.AUTO_RETRY.MAX_BACKOFF_INTERVAL_MS;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Validate resetAfterSuccess (default: true)
|
|
473
|
+
validated.resetAfterSuccess = autoRetry.resetAfterSuccess !== false;
|
|
474
|
+
|
|
475
|
+
// Validate consecutiveFailureThreshold
|
|
476
|
+
const threshold = Number(autoRetry.consecutiveFailureThreshold);
|
|
477
|
+
if (autoRetry.consecutiveFailureThreshold !== undefined && (!isNaN(threshold) && threshold >= constraints.CONSECUTIVE_FAILURE_THRESHOLD.MIN && threshold <= constraints.CONSECUTIVE_FAILURE_THRESHOLD.MAX)) {
|
|
478
|
+
validated.consecutiveFailureThreshold = threshold;
|
|
479
|
+
} else {
|
|
480
|
+
validated.consecutiveFailureThreshold = config.AUTO_RETRY.CONSECUTIVE_FAILURE_THRESHOLD;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return { valid: true, value: validated };
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Validate auto-save configuration
|
|
488
|
+
* @param {object} autoSave - Auto-save configuration to validate
|
|
489
|
+
* @returns {object} Validation result
|
|
490
|
+
*/
|
|
491
|
+
function validateAutoSave(autoSave) {
|
|
492
|
+
if (!autoSave || typeof autoSave !== 'object') {
|
|
493
|
+
// Return defaults if not provided
|
|
494
|
+
return {
|
|
495
|
+
valid: true,
|
|
496
|
+
value: {
|
|
497
|
+
enabled: config.AUTO_SAVE.ENABLED,
|
|
498
|
+
intervalMs: config.AUTO_SAVE.INTERVAL_MS,
|
|
499
|
+
saveOnExit: config.AUTO_SAVE.SAVE_ON_EXIT,
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const validated = {};
|
|
505
|
+
|
|
506
|
+
// Validate enabled (default: true)
|
|
507
|
+
validated.enabled = autoSave.enabled !== false;
|
|
508
|
+
|
|
509
|
+
// Validate intervalMs (must be between 5s and 5min)
|
|
510
|
+
const interval = Number(autoSave.intervalMs);
|
|
511
|
+
if (!isNaN(interval) && interval >= 5000 && interval <= 300000) {
|
|
512
|
+
validated.intervalMs = interval;
|
|
513
|
+
} else {
|
|
514
|
+
validated.intervalMs = config.AUTO_SAVE.INTERVAL_MS;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Validate saveOnExit (default: true)
|
|
518
|
+
validated.saveOnExit = autoSave.saveOnExit !== false;
|
|
519
|
+
|
|
520
|
+
return { valid: true, value: validated };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Validate export schedule configuration
|
|
525
|
+
* @param {object} exportSchedule - Export schedule configuration to validate
|
|
526
|
+
* @returns {object} Validation result
|
|
527
|
+
*/
|
|
528
|
+
function validateExportSchedule(exportSchedule) {
|
|
529
|
+
if (!exportSchedule || typeof exportSchedule !== 'object') {
|
|
530
|
+
// Return defaults if not provided
|
|
531
|
+
return {
|
|
532
|
+
valid: true,
|
|
533
|
+
value: {
|
|
534
|
+
enabled: EXPORT_SCHEDULE.ENABLED,
|
|
535
|
+
format: EXPORT_SCHEDULE.DEFAULT_FORMAT,
|
|
536
|
+
schedule: EXPORT_SCHEDULE.DEFAULT_SCHEDULE,
|
|
537
|
+
retentionDays: EXPORT_SCHEDULE.DEFAULT_RETENTION_DAYS,
|
|
538
|
+
directory: null,
|
|
539
|
+
includeMetrics: true,
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const validated = {};
|
|
545
|
+
const errors = [];
|
|
546
|
+
|
|
547
|
+
// Validate enabled (default: false)
|
|
548
|
+
validated.enabled = Boolean(exportSchedule.enabled);
|
|
549
|
+
|
|
550
|
+
// Validate format
|
|
551
|
+
if (exportSchedule.format !== undefined) {
|
|
552
|
+
if (['json', 'csv'].includes(exportSchedule.format)) {
|
|
553
|
+
validated.format = exportSchedule.format;
|
|
554
|
+
} else {
|
|
555
|
+
errors.push(`Invalid format: ${exportSchedule.format}`);
|
|
556
|
+
validated.format = EXPORT_SCHEDULE.DEFAULT_FORMAT;
|
|
557
|
+
}
|
|
558
|
+
} else {
|
|
559
|
+
validated.format = EXPORT_SCHEDULE.DEFAULT_FORMAT;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Validate schedule (cron expression)
|
|
563
|
+
if (exportSchedule.schedule !== undefined) {
|
|
564
|
+
try {
|
|
565
|
+
CronParser.parse(exportSchedule.schedule);
|
|
566
|
+
validated.schedule = exportSchedule.schedule;
|
|
567
|
+
} catch (err) {
|
|
568
|
+
errors.push(`Invalid cron expression: ${exportSchedule.schedule}`);
|
|
569
|
+
validated.schedule = EXPORT_SCHEDULE.DEFAULT_SCHEDULE;
|
|
570
|
+
}
|
|
571
|
+
} else {
|
|
572
|
+
validated.schedule = EXPORT_SCHEDULE.DEFAULT_SCHEDULE;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Validate retention days
|
|
576
|
+
if (exportSchedule.retentionDays !== undefined) {
|
|
577
|
+
const days = Number(exportSchedule.retentionDays);
|
|
578
|
+
if (!isNaN(days) && days >= EXPORT_SCHEDULE.MIN_RETENTION_DAYS && days <= EXPORT_SCHEDULE.MAX_RETENTION_DAYS) {
|
|
579
|
+
validated.retentionDays = days;
|
|
580
|
+
} else {
|
|
581
|
+
errors.push(`retentionDays must be ${EXPORT_SCHEDULE.MIN_RETENTION_DAYS}-${EXPORT_SCHEDULE.MAX_RETENTION_DAYS}`);
|
|
582
|
+
validated.retentionDays = EXPORT_SCHEDULE.DEFAULT_RETENTION_DAYS;
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
validated.retentionDays = EXPORT_SCHEDULE.DEFAULT_RETENTION_DAYS;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Validate directory
|
|
589
|
+
if (exportSchedule.directory !== undefined && exportSchedule.directory !== null) {
|
|
590
|
+
if (typeof exportSchedule.directory === 'string') {
|
|
591
|
+
const pathResult = validatePath(exportSchedule.directory, false);
|
|
592
|
+
if (pathResult.valid) {
|
|
593
|
+
validated.directory = pathResult.resolvedPath;
|
|
594
|
+
} else {
|
|
595
|
+
errors.push(`Invalid directory: ${pathResult.error}`);
|
|
596
|
+
validated.directory = null;
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
errors.push('directory must be a string or null');
|
|
600
|
+
validated.directory = null;
|
|
601
|
+
}
|
|
602
|
+
} else {
|
|
603
|
+
validated.directory = null;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Validate includeMetrics
|
|
607
|
+
validated.includeMetrics = exportSchedule.includeMetrics !== false;
|
|
608
|
+
|
|
609
|
+
if (errors.length > 0) {
|
|
610
|
+
logger.warn(`Export schedule validation warnings: ${errors.join('; ')}`);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return { valid: true, value: validated };
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Validate all settings at once
|
|
618
|
+
* @param {object} settings - Settings object to validate
|
|
619
|
+
* @returns {object} Validation result with validated settings
|
|
620
|
+
*/
|
|
621
|
+
function validateSettings(settings) {
|
|
622
|
+
if (!settings || typeof settings !== 'object') {
|
|
623
|
+
logger.warn('Settings must be an object, using defaults');
|
|
624
|
+
return getDefaultSettings();
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const validated = {};
|
|
628
|
+
const errors = [];
|
|
629
|
+
|
|
630
|
+
// Validate each setting
|
|
631
|
+
const validators = {
|
|
632
|
+
refreshInterval: validateRefreshInterval,
|
|
633
|
+
logLevelFilter: validateLogLevelFilter,
|
|
634
|
+
sessionSortMode: validateSessionSortMode,
|
|
635
|
+
theme: validateTheme,
|
|
636
|
+
exportFormat: validateExportFormat,
|
|
637
|
+
exportDirectory: validateExportDirectory,
|
|
638
|
+
showWidget1: validateWidgetVisibility,
|
|
639
|
+
showWidget2: validateWidgetVisibility,
|
|
640
|
+
showWidget3: validateWidgetVisibility,
|
|
641
|
+
showWidget4: validateWidgetVisibility,
|
|
642
|
+
showWidget5: validateWidgetVisibility,
|
|
643
|
+
showWidget6: validateWidgetVisibility,
|
|
644
|
+
showWidget7: validateWidgetVisibility,
|
|
645
|
+
pinnedWidgets: validatePinnedWidgets,
|
|
646
|
+
widgetOrder: validateWidgetOrder,
|
|
647
|
+
widgetSizes: validateWidgetSizes,
|
|
648
|
+
exportSchedule: validateExportSchedule,
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
for (const [key, validator] of Object.entries(validators)) {
|
|
652
|
+
const result = validator(settings[key]);
|
|
653
|
+
if (result.valid) {
|
|
654
|
+
validated[key] = result.value;
|
|
655
|
+
} else {
|
|
656
|
+
errors.push(`${key}: ${result.error}`);
|
|
657
|
+
// Use default value
|
|
658
|
+
validated[key] = getDefaultValue(key);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Validate autoRetry configuration separately
|
|
663
|
+
const autoRetryResult = validateAutoRetry(settings.autoRetry);
|
|
664
|
+
if (autoRetryResult.valid) {
|
|
665
|
+
validated.autoRetry = autoRetryResult.value;
|
|
666
|
+
} else {
|
|
667
|
+
errors.push(`autoRetry: ${autoRetryResult.error}`);
|
|
668
|
+
validated.autoRetry = autoRetryResult.value; // Uses defaults
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Validate autoSave configuration separately
|
|
672
|
+
const autoSaveResult = validateAutoSave(settings.autoSave);
|
|
673
|
+
if (autoSaveResult.valid) {
|
|
674
|
+
validated.autoSave = autoSaveResult.value;
|
|
675
|
+
} else {
|
|
676
|
+
errors.push(`autoSave: ${autoSaveResult.error}`);
|
|
677
|
+
validated.autoSave = autoSaveResult.value; // Uses defaults
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Validate exportSchedule configuration separately
|
|
681
|
+
const exportScheduleResult = validateExportSchedule(settings.exportSchedule);
|
|
682
|
+
if (exportScheduleResult.valid) {
|
|
683
|
+
validated.exportSchedule = exportScheduleResult.value;
|
|
684
|
+
} else {
|
|
685
|
+
errors.push(`exportSchedule: ${exportScheduleResult.error}`);
|
|
686
|
+
validated.exportSchedule = exportScheduleResult.value; // Uses defaults
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (errors.length > 0) {
|
|
690
|
+
logger.warn(`Settings validation errors: ${errors.join('; ')}`);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return { valid: true, value: validated };
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Get default value for a setting
|
|
698
|
+
* @param {string} key - Setting key
|
|
699
|
+
* @returns {any} Default value
|
|
700
|
+
*/
|
|
701
|
+
function getDefaultValue(key) {
|
|
702
|
+
const defaults = {
|
|
703
|
+
refreshInterval: config.REFRESH_INTERVALS.DEFAULT,
|
|
704
|
+
logLevelFilter: 'all',
|
|
705
|
+
sessionSortMode: 'time',
|
|
706
|
+
theme: 'default',
|
|
707
|
+
exportFormat: 'json',
|
|
708
|
+
exportDirectory: config.PATHS.EXPORTS,
|
|
709
|
+
autoRetry: {
|
|
710
|
+
enabled: config.AUTO_RETRY.ENABLED,
|
|
711
|
+
intervalMs: config.AUTO_RETRY.DEFAULT_INTERVAL_MS,
|
|
712
|
+
exponentialBackoff: config.AUTO_RETRY.EXPONENTIAL_BACKOFF,
|
|
713
|
+
backoffMultiplier: config.AUTO_RETRY.BACKOFF_MULTIPLIER,
|
|
714
|
+
maxBackoffIntervalMs: config.AUTO_RETRY.MAX_BACKOFF_INTERVAL_MS,
|
|
715
|
+
resetAfterSuccess: config.AUTO_RETRY.RESET_AFTER_SUCCESS,
|
|
716
|
+
consecutiveFailureThreshold: config.AUTO_RETRY.CONSECUTIVE_FAILURE_THRESHOLD,
|
|
717
|
+
},
|
|
718
|
+
showWidget1: true,
|
|
719
|
+
showWidget2: true,
|
|
720
|
+
showWidget3: true,
|
|
721
|
+
showWidget4: true,
|
|
722
|
+
showWidget5: true,
|
|
723
|
+
showWidget6: true,
|
|
724
|
+
showWidget7: true,
|
|
725
|
+
pinnedWidgets: [],
|
|
726
|
+
widgetOrder: [],
|
|
727
|
+
exportSchedule: {
|
|
728
|
+
enabled: EXPORT_SCHEDULE.ENABLED,
|
|
729
|
+
format: EXPORT_SCHEDULE.DEFAULT_FORMAT,
|
|
730
|
+
schedule: EXPORT_SCHEDULE.DEFAULT_SCHEDULE,
|
|
731
|
+
retentionDays: EXPORT_SCHEDULE.DEFAULT_RETENTION_DAYS,
|
|
732
|
+
directory: null,
|
|
733
|
+
includeMetrics: true,
|
|
734
|
+
},
|
|
735
|
+
};
|
|
736
|
+
return defaults[key];
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Get all default settings
|
|
741
|
+
* @returns {object} Default settings object
|
|
742
|
+
*/
|
|
743
|
+
function getDefaultSettings() {
|
|
744
|
+
return {
|
|
745
|
+
refreshInterval: config.REFRESH_INTERVALS.DEFAULT,
|
|
746
|
+
versionCheckInterval: 43200000, // 12 hours in milliseconds
|
|
747
|
+
lastVersionCheck: 0,
|
|
748
|
+
logLevelFilter: 'all',
|
|
749
|
+
sessionSortMode: 'time',
|
|
750
|
+
theme: 'default',
|
|
751
|
+
exportFormat: 'json',
|
|
752
|
+
exportDirectory: config.PATHS.EXPORTS,
|
|
753
|
+
showWidget1: true,
|
|
754
|
+
showWidget2: true,
|
|
755
|
+
showWidget3: true,
|
|
756
|
+
showWidget4: true,
|
|
757
|
+
showWidget5: true,
|
|
758
|
+
showWidget6: true,
|
|
759
|
+
showWidget7: true,
|
|
760
|
+
showWidget8: true,
|
|
761
|
+
showPerformanceMetrics: false,
|
|
762
|
+
sessionSearchQuery: '',
|
|
763
|
+
favorites: {},
|
|
764
|
+
showFavoritesOnly: false,
|
|
765
|
+
pinnedWidgets: [],
|
|
766
|
+
widgetOrder: [],
|
|
767
|
+
widgetSizes: {
|
|
768
|
+
cpu: 'medium',
|
|
769
|
+
mem: 'medium',
|
|
770
|
+
gpu: 'medium',
|
|
771
|
+
net: 'medium',
|
|
772
|
+
disk: 'medium',
|
|
773
|
+
sys: 'medium',
|
|
774
|
+
uptime: 'medium',
|
|
775
|
+
health: 'medium',
|
|
776
|
+
gateway: 'medium',
|
|
777
|
+
},
|
|
778
|
+
firstRun: true,
|
|
779
|
+
gatewayEndpoints: [{
|
|
780
|
+
name: 'local',
|
|
781
|
+
host: 'localhost',
|
|
782
|
+
port: 18789,
|
|
783
|
+
token: null,
|
|
784
|
+
enabled: true,
|
|
785
|
+
type: 'local'
|
|
786
|
+
}],
|
|
787
|
+
activeGatewayEndpoint: 'local',
|
|
788
|
+
webInterface: {
|
|
789
|
+
enabled: false,
|
|
790
|
+
port: config.WEB.DEFAULT_PORT,
|
|
791
|
+
host: config.WEB.HOST,
|
|
792
|
+
cors: true
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Validate gateway endpoint configuration
|
|
799
|
+
* @param {object} endpoint - Endpoint configuration to validate
|
|
800
|
+
* @returns {object} Validation result
|
|
801
|
+
*/
|
|
802
|
+
function validateGatewayEndpoint(endpoint) {
|
|
803
|
+
if (!endpoint || typeof endpoint !== 'object') {
|
|
804
|
+
return { valid: false, error: 'Endpoint must be an object' };
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Validate name
|
|
808
|
+
if (!endpoint.name || typeof endpoint.name !== 'string' || endpoint.name.length === 0) {
|
|
809
|
+
return { valid: false, error: 'Endpoint name is required and must be a non-empty string' };
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (endpoint.name.length > config.VALIDATION.ENDPOINT_NAME.MAX_LENGTH) {
|
|
813
|
+
return { valid: false, error: `Endpoint name must be at most ${config.VALIDATION.ENDPOINT_NAME.MAX_LENGTH} characters` };
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (!config.VALIDATION.ENDPOINT_NAME.PATTERN.test(endpoint.name)) {
|
|
817
|
+
return { valid: false, error: 'Endpoint name must contain only alphanumeric characters, underscores, and hyphens' };
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// Validate host
|
|
821
|
+
if (!endpoint.host || typeof endpoint.host !== 'string' || endpoint.host.length === 0) {
|
|
822
|
+
return { valid: false, error: 'Endpoint host is required and must be a non-empty string' };
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Validate port
|
|
826
|
+
const port = Number(endpoint.port);
|
|
827
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
828
|
+
return { valid: false, error: 'Endpoint port must be a valid port number (1-65535)' };
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Validate type if provided
|
|
832
|
+
if (endpoint.type !== undefined) {
|
|
833
|
+
if (!config.VALIDATION.VALID_ENDPOINT_TYPES.includes(endpoint.type)) {
|
|
834
|
+
return { valid: false, error: `Endpoint type must be one of: ${config.VALIDATION.VALID_ENDPOINT_TYPES.join(', ')}` };
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Validate enabled if provided (should be boolean)
|
|
839
|
+
if (endpoint.enabled !== undefined && typeof endpoint.enabled !== 'boolean') {
|
|
840
|
+
return { valid: false, error: 'Endpoint enabled must be a boolean' };
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Validate token if provided (should be string or null)
|
|
844
|
+
if (endpoint.token !== undefined && endpoint.token !== null && typeof endpoint.token !== 'string') {
|
|
845
|
+
return { valid: false, error: 'Endpoint token must be a string or null' };
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
return {
|
|
849
|
+
valid: true,
|
|
850
|
+
value: {
|
|
851
|
+
name: endpoint.name,
|
|
852
|
+
host: endpoint.host,
|
|
853
|
+
port: port,
|
|
854
|
+
token: endpoint.token || null,
|
|
855
|
+
enabled: endpoint.enabled !== false, // default true
|
|
856
|
+
type: endpoint.type || 'local'
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Validate a single value against a type
|
|
863
|
+
* @param {any} value - Value to validate
|
|
864
|
+
* @param {string} type - Expected type
|
|
865
|
+
* @returns {boolean} Whether valid
|
|
866
|
+
*/
|
|
867
|
+
function validateType(value, type) {
|
|
868
|
+
switch (type) {
|
|
869
|
+
case 'number':
|
|
870
|
+
return typeof value === 'number' && !isNaN(value);
|
|
871
|
+
case 'string':
|
|
872
|
+
return typeof value === 'string';
|
|
873
|
+
case 'boolean':
|
|
874
|
+
return typeof value === 'boolean';
|
|
875
|
+
case 'object':
|
|
876
|
+
return typeof value === 'object' && value !== null;
|
|
877
|
+
default:
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
export {
|
|
883
|
+
validateSettings,
|
|
884
|
+
validateRefreshInterval,
|
|
885
|
+
validateLogLevelFilter,
|
|
886
|
+
validateSessionSortMode,
|
|
887
|
+
validateTheme,
|
|
888
|
+
validateExportFormat,
|
|
889
|
+
validateExportDirectory,
|
|
890
|
+
validateWidgetVisibility,
|
|
891
|
+
validateAlertThresholds,
|
|
892
|
+
validateAutoRetry,
|
|
893
|
+
validateAutoSave,
|
|
894
|
+
validateExportSchedule,
|
|
895
|
+
validatePath,
|
|
896
|
+
validateType,
|
|
897
|
+
validateGatewayEndpoint,
|
|
898
|
+
getDefaultSettings,
|
|
899
|
+
VALID_THEMES,
|
|
900
|
+
VALID_SORT_MODES,
|
|
901
|
+
VALID_LOG_LEVELS,
|
|
902
|
+
VALID_EXPORT_FORMATS
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
export default {
|
|
906
|
+
validateSettings,
|
|
907
|
+
validateRefreshInterval,
|
|
908
|
+
validateLogLevelFilter,
|
|
909
|
+
validateSessionSortMode,
|
|
910
|
+
validateTheme,
|
|
911
|
+
validateExportFormat,
|
|
912
|
+
validateExportDirectory,
|
|
913
|
+
validateWidgetVisibility,
|
|
914
|
+
validateAlertThresholds,
|
|
915
|
+
validateAutoRetry,
|
|
916
|
+
validateAutoSave,
|
|
917
|
+
validateExportSchedule,
|
|
918
|
+
validatePath,
|
|
919
|
+
validateType,
|
|
920
|
+
validateGatewayEndpoint,
|
|
921
|
+
getDefaultSettings,
|
|
922
|
+
VALID_THEMES,
|
|
923
|
+
VALID_SORT_MODES,
|
|
924
|
+
VALID_LOG_LEVELS,
|
|
925
|
+
VALID_EXPORT_FORMATS
|
|
926
|
+
};
|