i18n: - Expanded from 6 to 31 languages (no Hebrew per policy) - Added: pt, ru, ja, ko, hi, tr, it, nl, pl, sv, id, uk, th, vi, fa, cs, ms, ro, el, bn, hu, fi, da, no, ur - RTL support for ar, fa, ur - Language selector dropdown wired into dashboard navbar Disk Safety: - New backend route /api/v1/disk-settings (GET/POST/cleanup) - Frontend modal with sliders for health interval, max entries, retention days - Clean Up Now button triggers immediate cleanup - Wired into dashboard navbar Desktop Auto-Updater (from timed-out subagent): - electron-updater installed and configured - Checks get.dashcaddy.net/release/ for updates - Publish config added to package.json VM Uninstall: - Wizard calls vmDestroy before regular uninstall - Cleans up VM/disk sandbox on uninstall Cleanup: - Recursive data nesting guard (nesting-guard.js) - Removed 242MB of data/data/data/ duplicates
99 lines
4.4 KiB
JavaScript
99 lines
4.4 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// GET current disk settings + actual disk usage
|
|
router.get('/', (req, res) => {
|
|
try {
|
|
const settings = {
|
|
healthCheckInterval: parseInt(process.env.HEALTH_CHECK_INTERVAL || '30000'),
|
|
healthMaxEntries: parseInt(process.env.HEALTH_MAX_ENTRIES || '500'),
|
|
healthRetentionDays: parseInt(process.env.HEALTH_HISTORY_RETENTION || '14'),
|
|
statsMaxEntries: parseInt(process.env.CONTAINER_STATS_MAX_ENTRIES || '500'),
|
|
auditMaxEntries: parseInt(process.env.AUDIT_MAX_ENTRIES || '1000'),
|
|
backupMaxStorageBytes: parseInt(process.env.BACKUP_MAX_STORAGE_BYTES || '0'),
|
|
};
|
|
|
|
// Get actual disk usage
|
|
let diskUsage = { total: 0, used: 0, free: 0, dataDirSize: 0 };
|
|
try {
|
|
const { execSync } = require('child_process');
|
|
const dfOut = execSync("df -B1 /opt/dashcaddy/dashcaddy-api/data 2>/dev/null || df -B1 / 2>/dev/null").toString().trim().split('\n');
|
|
if (dfOut.length > 1) {
|
|
const parts = dfOut[1].split(/\s+/);
|
|
diskUsage.total = parseInt(parts[1]) || 0;
|
|
diskUsage.used = parseInt(parts[2]) || 0;
|
|
diskUsage.free = parseInt(parts[3]) || 0;
|
|
}
|
|
const duOut = execSync("du -sb /opt/dashcaddy/dashcaddy-api/data 2>/dev/null || echo 0").toString().trim().split(/\s+/);
|
|
diskUsage.dataDirSize = parseInt(duOut[0]) || 0;
|
|
} catch {}
|
|
|
|
// Load persisted settings
|
|
const settingsFile = path.join(path.dirname(require('../config/paths').configFile), 'disk-settings.json');
|
|
let persisted = {};
|
|
try { persisted = JSON.parse(fs.readFileSync(settingsFile, 'utf8')); } catch {}
|
|
|
|
res.json({ success: true, current: { ...settings, ...persisted }, diskUsage });
|
|
} catch (e) {
|
|
res.status(500).json({ success: false, error: e.message });
|
|
}
|
|
});
|
|
|
|
// POST update settings
|
|
router.post('/', (req, res) => {
|
|
try {
|
|
const { healthInterval, healthMaxEntries, healthRetentionDays, statsMaxEntries, auditMaxEntries } = req.body;
|
|
const updates = {};
|
|
|
|
if (healthInterval !== undefined) { updates.healthCheckInterval = parseInt(healthInterval); process.env.HEALTH_CHECK_INTERVAL = String(healthInterval); }
|
|
if (healthMaxEntries !== undefined) { updates.healthMaxEntries = parseInt(healthMaxEntries); process.env.HEALTH_MAX_ENTRIES = String(healthMaxEntries); }
|
|
if (healthRetentionDays !== undefined) { updates.healthRetentionDays = parseInt(healthRetentionDays); process.env.HEALTH_HISTORY_RETENTION = String(healthRetentionDays); }
|
|
if (statsMaxEntries !== undefined) { updates.statsMaxEntries = parseInt(statsMaxEntries); process.env.CONTAINER_STATS_MAX_ENTRIES = String(statsMaxEntries); }
|
|
if (auditMaxEntries !== undefined) { updates.auditMaxEntries = parseInt(auditMaxEntries); process.env.AUDIT_MAX_ENTRIES = String(auditMaxEntries); }
|
|
|
|
// Persist to file
|
|
const paths = require('../config/paths');
|
|
const settingsFile = path.join(path.dirname(paths.configFile), 'disk-settings.json');
|
|
let existing = {};
|
|
try { existing = JSON.parse(fs.readFileSync(settingsFile, 'utf8')); } catch {}
|
|
fs.writeFileSync(settingsFile, JSON.stringify({ ...existing, ...updates }, null, 2));
|
|
|
|
res.json({ success: true, updated: updates, message: 'Settings saved. Some changes apply on next container restart.' });
|
|
} catch (e) {
|
|
res.status(500).json({ success: false, error: e.message });
|
|
}
|
|
});
|
|
|
|
// POST trigger immediate cleanup
|
|
router.post('/cleanup', async (req, res) => {
|
|
try {
|
|
const results = { cleaned: {} };
|
|
|
|
// Clean health history
|
|
try {
|
|
const healthChecker = require('../monitoring/health-checker');
|
|
if (healthChecker.instance && healthChecker.instance.cleanupHistory) {
|
|
healthChecker.instance.cleanupHistory();
|
|
results.cleaned.healthHistory = 'Cleaned old entries';
|
|
}
|
|
} catch (e) { results.cleaned.healthHistory = 'Skipped: ' + e.message; }
|
|
|
|
// Clean container stats
|
|
try {
|
|
const resourceMonitor = require('../managers/resource-monitor');
|
|
if (resourceMonitor.instance && resourceMonitor.instance.cleanupOldStats) {
|
|
resourceMonitor.instance.cleanupOldStats();
|
|
results.cleaned.containerStats = 'Cleaned old entries';
|
|
}
|
|
} catch (e) { results.cleaned.containerStats = 'Skipped: ' + e.message; }
|
|
|
|
res.json({ success: true, results });
|
|
} catch (e) {
|
|
res.status(500).json({ success: false, error: e.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|