Seregon/zftpd

Zero-copy FTP/HTTP Daemon compatible with all POSIX systems

C/11.0 KB/No license
web/js/views/settings.js
zftpd / web / js / views / settings.js
1/* ══ SETTINGS VIEW ════════════════════════════════════════════════════════
2 * User preferences panel. All settings persist in localStorage.
3 * ES5 compatible for PS5 browser.
4 *
5 * ┌─────────────────────────────────────────────────┐
6 * │ ⚙ Settings │
7 * │ │
8 * │ ┌─ Display ──────────────────────────────────┐ │
9 * │ │ Theme [PS5 ▾] │ │
10 * │ │ Default View [Grid ▾] │ │
11 * │ │ Animations [●━━━] │ │
12 * │ └────────────────────────────────────────────┘ │
13 * │ │
14 * │ ┌─ File Explorer ────────────────────────────┐ │
15 * │ │ Show System Folders [━━━○] │ │
16 * │ │ Show Hidden Files [━━━○] │ │
17 * │ └────────────────────────────────────────────┘ │
18 * │ │
19 * │ ┌─ About ────────────────────────────────────┐ │
20 * │ │ zftpd v1.5.0 │ │
21 * │ │ Console File Manager │ │
22 * │ │ github.com/seregonwar/zftpd │ │
23 * │ └────────────────────────────────────────────┘ │
24 * └─────────────────────────────────────────────────┘
25 * ═════════════════════════════════════════════════════════════════════════ */
26 
27var ZFTPD = ZFTPD || {};
28 
29(function (Z) {
30 'use strict';
31 
32 var D = document;
33 var ICO = Z.ICO;
34 
35 /* ══════════════════════════════════════════════════════════════════════
36 * DEFAULT SETTINGS
37 *
38 * Dangerous PS4/PS5 system folders that can brick the console
39 * if modified by an inexperienced user:
40 *
41 * /system — Core OS binaries
42 * /system_ex — Extended OS (updates, recovery)
43 * /preinst — Pre-installed system data
44 * /preinst2 — Secondary pre-install partition
45 * /mini-syscore — Minimal system core (safe mode)
46 * /sandcastle — Sandbox environment
47 * /update — System update staging area
48 * /dev — Device nodes
49 * /proc — Process filesystem
50 * ══════════════════════════════════════════════════════════════════════ */
51 
52 var DEFAULTS = {
53 showSystemFolders: false,
54 showHiddenFiles: false,
55 defaultView: 'grid',
56 animationsEnabled: true
57 };
58 
59 var DANGEROUS_FOLDERS = [
60 'system', 'system_ex', 'preinst', 'preinst2',
61 'mini-syscore', 'sandcastle', 'update',
62 'dev', 'proc'
63 ];
64 
65 /* ── Load / Save ── */
66 
67 Z.settings = {};
68 
69 Z.loadSettings = function () {
70 var saved = {};
71 try {
72 var raw = localStorage.getItem('zftpd_settings');
73 if (raw) saved = JSON.parse(raw);
74 } catch (e) { /* ignore */ }
75 
76 var key;
77 for (key in DEFAULTS) {
78 if (DEFAULTS.hasOwnProperty(key)) {
79 Z.settings[key] = saved.hasOwnProperty(key) ? saved[key] : DEFAULTS[key];
80 }
81 }
82 };
83 
84 Z.saveSettings = function () {
85 try {
86 localStorage.setItem('zftpd_settings', JSON.stringify(Z.settings));
87 } catch (e) { /* ignore */ }
88 };
89 
90 /* ── Dangerous folder check ── */
91 
92 Z.isDangerousFolder = function (name) {
93 var lower = (name || '').toLowerCase();
94 for (var i = 0; i < DANGEROUS_FOLDERS.length; i++) {
95 if (lower === DANGEROUS_FOLDERS[i]) return true;
96 }
97 return false;
98 };
99 
100 Z.isHiddenFile = function (name) {
101 return (name || '').charAt(0) === '.';
102 };
103 
104 /* ── Init on load ── */
105 Z.loadSettings();
106 
107 /* ══════════════════════════════════════════════════════════════════════
108 * SETTINGS VIEW RENDERER
109 * ══════════════════════════════════════════════════════════════════════ */
110 
111 var settings = {};
112 
113 settings.refresh = function () {
114 var container = D.getElementById('settings-content');
115 if (!container) return;
116 
117 Z.loadSettings();
118 
119 container.innerHTML =
120 /* ── Header ── */
121 '<div class="sett-header">' +
122 '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>' +
123 '<span>Settings</span>' +
124 '</div>' +
125 
126 /* ══ Display Section ══ */
127 '<div class="sett-section">' +
128 '<div class="sett-section-title">' +
129 '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/></svg>' +
130 ' Display' +
131 '</div>' +
132 
133 /* Theme selector */
134 '<div class="sett-row">' +
135 '<div class="sett-label">' +
136 '<div class="sett-name">Theme</div>' +
137 '<div class="sett-desc">Choose your color scheme</div>' +
138 '</div>' +
139 '<select id="sett-theme" class="sett-select">' +
140 _buildThemeOptions() +
141 '</select>' +
142 '</div>' +
143 
144 /* Default View */
145 '<div class="sett-row">' +
146 '<div class="sett-label">' +
147 '<div class="sett-name">Default View</div>' +
148 '<div class="sett-desc">File explorer layout mode</div>' +
149 '</div>' +
150 '<select id="sett-default-view" class="sett-select">' +
151 '<option value="grid"' + (Z.settings.defaultView === 'grid' ? ' selected' : '') + '>Grid</option>' +
152 '<option value="list"' + (Z.settings.defaultView === 'list' ? ' selected' : '') + '>List</option>' +
153 '<option value="details"' + (Z.settings.defaultView === 'details' ? ' selected' : '') + '>Details</option>' +
154 '</select>' +
155 '</div>' +
156 
157 /* Animations */
158 _buildToggleRow('sett-animations', 'Animations', 'Smooth transitions and micro-animations', Z.settings.animationsEnabled) +
159 
160 '</div>' +
161 
162 /* ══ File Explorer Section ══ */
163 '<div class="sett-section">' +
164 '<div class="sett-section-title">' +
165 '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>' +
166 ' File Explorer' +
167 '</div>' +
168 
169 _buildToggleRow('sett-sys-folders', 'Show System Folders',
170 'Reveal system, system_ex, preinst and other protected directories. Modifying these can brick your console.',
171 Z.settings.showSystemFolders, true) +
172 
173 _buildToggleRow('sett-hidden', 'Show Hidden Files',
174 'Show files and folders starting with a dot (.)',
175 Z.settings.showHiddenFiles) +
176 
177 '</div>' +
178 
179 /* ══ Hardware & System ══ */
180 '<div class="sett-section">' +
181 '<div class="sett-section-title">' +
182 '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="16" height="16" rx="2" ry="2"/><rect x="9" y="9" width="6" height="6"/><line x1="9" y1="1" x2="9" y2="4"/><line x1="15" y1="1" x2="15" y2="4"/><line x1="9" y1="20" x2="9" y2="23"/><line x1="15" y1="20" x2="15" y2="23"/><line x1="20" y1="9" x2="23" y2="9"/><line x1="20" y1="14" x2="23" y2="14"/><line x1="1" y1="9" x2="4" y2="9"/><line x1="1" y1="14" x2="4" y2="14"/></svg>' +
183 ' Console Hardware' +
184 '</div>' +
185 _buildSliderRow('sett-fan-speed', 'Target Temperature',
186 'Threshold for SMC fan control algorithm. A lower temp generally means higher RPM. (PS4/PS5 payload exclusive)',
187 Z.settings.fanThreshold || 60, 40, 90, '°C') +
188 '</div>' +
189 
190 /* ══ Danger Zone ══ */
191 '<div class="sett-section sett-danger-info">' +
192 '<div class="sett-section-title">' +
193 '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>' +
194 ' Protected Directories' +
195 '</div>' +
196 '<div class="sett-danger-list">' +
197 _buildDangerList() +
198 '</div>' +
199 '</div>' +
200 
201 /* ══ About Section ══ */
202 '<div class="sett-section">' +
203 '<div class="sett-section-title">' +
204 '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>' +
205 ' About' +
206 '</div>' +
207 '<div class="sett-about">' +
208 '<div class="sett-about-logo">' +
209 '<img src="assets/zftpd-logo.png" alt="zftpd" style="width:48px;height:48px;object-fit:contain;">' +
210 '</div>' +
211 '<div class="sett-about-info">' +
212 '<div class="sett-about-name">zftpd</div>' +
213 '<div class="sett-about-ver">v1.5.0</div>' +
214 '<div class="sett-about-desc">The ultimate PS4/PS5 file manager.<br>FTP + HTTP + PKG installer.</div>' +
215 '<div class="sett-about-link">' +
216 '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65S8.93 17.38 9 18v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg>' +
217 ' github.com/seregonwar/zftpd' +
218 '</div>' +
219 '</div>' +
220 '</div>' +
221 '</div>';
222 
223 /* ── Wire up event handlers ── */
224 _bindEvents();
225 };
226 
227 /* ── HTML Helpers ── */
228 
229 function _buildThemeOptions() {
230 var cur = D.documentElement.getAttribute('data-theme') || 'ps5';
231 var html = '';
232 for (var i = 0; i < Z.THEMES.length; i++) {
233 var t = Z.THEMES[i];
234 html += '<option value="' + t.id + '"' + (t.id === cur ? ' selected' : '') + '>' + t.name + ' — ' + t.desc + '</option>';
235 }
236 return html;
237 }
238 
239 function _buildToggleRow(id, name, desc, checked, isDanger) {
240 return '<div class="sett-row">' +
241 '<div class="sett-label">' +
242 '<div class="sett-name">' + (isDanger ? '<span class="sett-warn-dot"></span>' : '') + name + '</div>' +
243 '<div class="sett-desc">' + desc + '</div>' +
244 '</div>' +
245 '<label class="sett-toggle">' +
246 '<input type="checkbox" id="' + id + '"' + (checked ? ' checked' : '') + '>' +
247 '<span class="sett-toggle-track' + (isDanger ? ' danger' : '') + '">' +
248 '<span class="sett-toggle-thumb"></span>' +
249 '</span>' +
250 '</label>' +
251 '</div>';
252 }
253 
254 function _buildSliderRow(id, name, desc, value, min, max, unit, isDanger) {
255 return '<div class="sett-row" style="flex-wrap:wrap;">' +
256 '<div class="sett-label" style="flex:1 1 200px;">' +
257 '<div class="sett-name">' + (isDanger ? '<span class="sett-warn-dot"></span>' : '') + name + '</div>' +
258 '<div class="sett-desc">' + desc + '</div>' +
259 '</div>' +
260 '<div class="sett-slider-wrap" style="display:flex;align-items:center;gap:12px;flex:1 1 200px;justify-content:flex-end;">' +
261 '<input type="range" id="' + id + '" min="' + min + '" max="' + max + '" value="' + value + '" style="flex:auto;max-width:200px;">' +
262 '<span id="' + id + '-val" style="min-width:40px;text-align:right;font-weight:600;font-variant-numeric:tabular-nums;">' + value + unit + '</span>' +
263 '</div>' +
264 '</div>';
265 }
266 
267 function _buildDangerList() {
268 var html = '';
269 for (var i = 0; i < DANGEROUS_FOLDERS.length; i++) {
270 html += '<span class="sett-danger-tag">/' + DANGEROUS_FOLDERS[i] + '</span>';
271 }
272 return html;
273 }
274 
275 /* ── Event Binding ── */
276 
277 function _bindEvents() {
278 /* Theme */
279 var themeSel = D.getElementById('sett-theme');
280 if (themeSel) {
281 themeSel.onchange = function () {
282 Z.setTheme(this.value);
283 /* Re-render to update the theme selector */
284 };
285 }
286 
287 /* Default view */
288 var viewSel = D.getElementById('sett-default-view');
289 if (viewSel) {
290 viewSel.onchange = function () {
291 Z.settings.defaultView = this.value;
292 Z.saveSettings();
293 };
294 }
295 
296 /* Animations */
297 var animCb = D.getElementById('sett-animations');
298 if (animCb) {
299 animCb.onchange = function () {
300 Z.settings.animationsEnabled = this.checked;
301 Z.saveSettings();
302 /* Toggle CSS animations globally */
303 if (!this.checked) {
304 D.documentElement.style.setProperty('--transition-fast', '0s');
305 D.documentElement.style.setProperty('--transition-med', '0s');
306 D.documentElement.style.setProperty('--transition-slow', '0s');
307 } else {
308 D.documentElement.style.removeProperty('--transition-fast');
309 D.documentElement.style.removeProperty('--transition-med');
310 D.documentElement.style.removeProperty('--transition-slow');
311 }
312 };
313 }
314 
315 /* Show system folders */
316 var sysCb = D.getElementById('sett-sys-folders');
317 if (sysCb) {
318 sysCb.onchange = function () {
319 Z.settings.showSystemFolders = this.checked;
320 Z.saveSettings();
321 Z.toast(this.checked ? 'System folders visible' : 'System folders hidden', this.checked ? 'wn' : 'ok');
322 };
323 }
324 
325 /* Show hidden files */
326 var hidCb = D.getElementById('sett-hidden');
327 if (hidCb) {
328 hidCb.onchange = function () {
329 Z.settings.showHiddenFiles = this.checked;
330 Z.saveSettings();
331 Z.toast(this.checked ? 'Hidden files visible' : 'Hidden files hidden', 'ok');
332 };
333 }
334
335 /* Fan Speed */
336 var fanSlider = D.getElementById('sett-fan-speed');
337 if (fanSlider) {
338 fanSlider.onchange = function () {
339 var val = parseInt(this.value, 10);
340 Z.settings.fanThreshold = val;
341 Z.saveSettings();
342
343 fetch('/api/admin/fan?threshold=' + val)
344 .then(function(res) { return res.json(); })
345 .then(function(data) {
346 if (data.status === 'ok') {
347 Z.toast('Fan threshold updated to ' + val + '°C', 'ok');
348 } else {
349 Z.toast('Fan control failed: ' + (data.message || 'Error'), 'er');
350 }
351 })
352 .catch(function(e) {
353 Z.toast('Fan API is unreachable or blocked', 'er');
354 });
355 };
356
357 fanSlider.oninput = function () {
358 var valEl = D.getElementById('sett-fan-speed-val');
359 if (valEl) valEl.textContent = this.value + '°C';
360 };
361 }
362 }
363 
364 Z.settingsView = settings;
365 
366})(ZFTPD);
367