clay-server 5.1.0-beta.2 → 5.1.0-beta.3
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/bin/cli.js +9 -91
- package/lib/builtin-mates.js +7 -7
- package/lib/daemon-settings.js +11 -0
- package/lib/daemon.js +8 -37
- package/lib/linear-store.js +35 -8
- package/lib/mate-identity-file.js +39 -0
- package/lib/mate-knowledge-sync.js +1 -1
- package/lib/mate-ready-creation.js +2 -1
- package/lib/mates-identity.js +4 -4
- package/lib/mates-prompts.js +4 -3
- package/lib/mates.js +12 -11
- package/lib/os-users.js +5 -16
- package/lib/project-connection.js +0 -7
- package/lib/project-debate.js +3 -2
- package/lib/project-filesystem.js +21 -39
- package/lib/project-mate-interaction.js +7 -6
- package/lib/project-server-settings.js +31 -0
- package/lib/project-sessions.js +57 -184
- package/lib/project.js +3 -3
- package/lib/public/app.js +6 -37
- package/lib/public/css/input.css +3 -1
- package/lib/public/css/menus.css +3 -0
- package/lib/public/css/overlays.css +7 -0
- package/lib/public/css/project-settings-modal.css +56 -0
- package/lib/public/css/server-settings-system.css +125 -0
- package/lib/public/css/session-github.css +25 -16
- package/lib/public/css/settings-ui.css +247 -22
- package/lib/public/css/shared-browser.css +7 -3
- package/lib/public/css/title-bar.css +5 -1
- package/lib/public/index.html +173 -447
- package/lib/public/modules/account-island.js +9 -7
- package/lib/public/modules/admin-settings-email.js +197 -0
- package/lib/public/modules/admin-settings-invitations.js +183 -0
- package/lib/public/modules/admin-settings-projects.js +198 -0
- package/lib/public/modules/admin-settings-ui.js +100 -0
- package/lib/public/modules/admin-settings-users.js +331 -0
- package/lib/public/modules/admin.js +19 -949
- package/lib/public/modules/app-dm.js +0 -2
- package/lib/public/modules/app-messages.js +13 -52
- package/lib/public/modules/app-rate-limit.js +70 -12
- package/lib/public/modules/avatar.js +6 -1
- package/lib/public/modules/context-sources.js +27 -18
- package/lib/public/modules/default-ai.js +14 -7
- package/lib/public/modules/header-tui-font.js +10 -20
- package/lib/public/modules/home-mate-avatar-editor.js +2 -2
- package/lib/public/modules/linear-settings.js +3 -2
- package/lib/public/modules/markdown.js +5 -6
- package/lib/public/modules/notification-settings.js +81 -0
- package/lib/public/modules/notifications.js +44 -28
- package/lib/public/modules/personal-settings-dialog.js +51 -0
- package/lib/public/modules/profile.js +15 -6
- package/lib/public/modules/project-settings-environment.js +106 -0
- package/lib/public/modules/project-settings.js +72 -628
- package/lib/public/modules/server-settings-controls.js +316 -0
- package/lib/public/modules/server-settings-environment.js +194 -0
- package/lib/public/modules/server-settings-navigation.js +39 -0
- package/lib/public/modules/server-settings-state.js +54 -0
- package/lib/public/modules/server-settings.js +116 -788
- package/lib/public/modules/server-storage-settings.js +84 -0
- package/lib/public/modules/session-github.js +2 -1
- package/lib/public/modules/session-presence-ui.js +1 -1
- package/lib/public/modules/settings-env-values.js +34 -0
- package/lib/public/modules/settings-select.js +121 -0
- package/lib/public/modules/shared-browser.js +22 -12
- package/lib/public/modules/stt-picker.js +14 -5
- package/lib/public/modules/terminal-prefs.js +22 -0
- package/lib/public/modules/theme.js +88 -52
- package/lib/public/modules/user-settings-account.js +318 -0
- package/lib/public/modules/user-settings-appearance.js +55 -0
- package/lib/public/modules/user-settings-editors.js +24 -0
- package/lib/public/modules/user-settings-integrations.js +104 -0
- package/lib/public/modules/user-settings-layout.js +80 -0
- package/lib/public/modules/user-settings-preferences.js +48 -0
- package/lib/public/modules/user-settings-profile.js +53 -0
- package/lib/public/modules/user-settings-themes.js +38 -0
- package/lib/public/modules/user-settings-voice.js +75 -0
- package/lib/public/modules/user-settings.js +53 -480
- package/lib/public/style.css +4 -0
- package/lib/sdk-message-processor.js +16 -4
- package/lib/sdk-quota-snapshot.js +37 -0
- package/lib/server-appearance-settings.js +71 -0
- package/lib/server-linear.js +14 -8
- package/lib/server-mate-instructions.js +2 -2
- package/lib/server-mates.js +2 -1
- package/lib/server-notification-settings.js +67 -0
- package/lib/server-settings.js +4 -0
- package/lib/server-storage.js +57 -0
- package/lib/server.js +10 -2
- package/lib/sessions.js +3 -4
- package/lib/startup-projects.js +40 -0
- package/lib/storage-native-walk.js +85 -0
- package/lib/storage-usage.js +144 -0
- package/lib/users-preferences.js +0 -74
- package/lib/users.js +0 -4
- package/lib/ws-schema.js +2 -4
- package/lib/yoke/acp-process-manager.js +2 -2
- package/lib/yoke/adapters/codex.js +12 -52
- package/lib/yoke/codex-app-server.js +2 -2
- package/lib/yoke/codex-model-catalog.js +52 -0
- package/lib/yoke/codex-rate-limits.js +26 -0
- package/package.json +3 -3
|
@@ -1,806 +1,134 @@
|
|
|
1
|
-
|
|
1
|
+
import { initStorageUsage, loadStorageUsage } from './server-storage-settings.js';
|
|
2
|
+
// Full-app server administration shell. Page state survives navigation and closing.
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { getWs } from './ws-ref.js';
|
|
2
5
|
import { refreshIcons } from './icons.js';
|
|
3
|
-
import { showToast, copyToClipboard } from './utils.js';
|
|
4
|
-
import { parseEnvString, looksLikeEnv, serializeEnvValue } from './project-settings.js';
|
|
5
|
-
import { checkAdminAccess, loadAdminSection } from './admin.js';
|
|
6
6
|
import { closeFileViewer } from './filebrowser.js';
|
|
7
|
+
import { checkAdminAccess, loadAdminSection } from './admin.js';
|
|
8
|
+
import { openUserSettings } from './user-settings.js';
|
|
7
9
|
import { initNetworkSettings, loadNetworkSettings } from './server-network-settings.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// ESC to close
|
|
41
|
-
document.addEventListener("keydown", function (e) {
|
|
42
|
-
if (e.key === "Escape" && !settingsEl.classList.contains("hidden")) {
|
|
43
|
-
closeSettings();
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Nav item clicks
|
|
48
|
-
for (var i = 0; i < navItems.length; i++) {
|
|
49
|
-
navItems[i].addEventListener("click", function () {
|
|
50
|
-
var section = this.dataset.section;
|
|
51
|
-
switchSection(section);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Mobile dropdown nav
|
|
56
|
-
var navDropdown = document.getElementById("settings-nav-dropdown");
|
|
57
|
-
if (navDropdown) {
|
|
58
|
-
navDropdown.addEventListener("change", function () {
|
|
59
|
-
switchSection(this.value);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Copyable command blocks
|
|
64
|
-
var copyables = settingsEl.querySelectorAll(".settings-copyable");
|
|
65
|
-
for (var c = 0; c < copyables.length; c++) {
|
|
66
|
-
copyables[c].addEventListener("click", function () {
|
|
67
|
-
var text = this.dataset.copy;
|
|
68
|
-
if (!text) return;
|
|
69
|
-
var btn = this.querySelector(".settings-copy-btn");
|
|
70
|
-
copyToClipboard(text).then(function () {
|
|
71
|
-
if (btn) {
|
|
72
|
-
var orig = btn.textContent;
|
|
73
|
-
btn.textContent = "✓";
|
|
74
|
-
setTimeout(function () { btn.textContent = orig; }, 1500);
|
|
75
|
-
}
|
|
76
|
-
showToast("Copied to clipboard");
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Notification toggles
|
|
82
|
-
var notifAlert = document.getElementById("settings-notif-alert");
|
|
83
|
-
var notifSound = document.getElementById("settings-notif-sound");
|
|
84
|
-
var notifPush = document.getElementById("settings-notif-push");
|
|
85
|
-
|
|
86
|
-
if (notifAlert) {
|
|
87
|
-
notifAlert.addEventListener("change", function () {
|
|
88
|
-
var src = document.getElementById("notif-toggle-alert");
|
|
89
|
-
if (src) {
|
|
90
|
-
src.checked = this.checked;
|
|
91
|
-
src.dispatchEvent(new Event("change", { bubbles: true }));
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (notifSound) {
|
|
97
|
-
notifSound.addEventListener("change", function () {
|
|
98
|
-
var src = document.getElementById("notif-toggle-sound");
|
|
99
|
-
if (src) {
|
|
100
|
-
src.checked = this.checked;
|
|
101
|
-
src.dispatchEvent(new Event("change", { bubbles: true }));
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (notifPush) {
|
|
107
|
-
notifPush.addEventListener("change", function () {
|
|
108
|
-
var src = document.getElementById("notif-toggle-push");
|
|
109
|
-
if (src) {
|
|
110
|
-
src.checked = this.checked;
|
|
111
|
-
src.dispatchEvent(new Event("change", { bubbles: true }));
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// PIN buttons
|
|
117
|
-
var pinSetBtn = document.getElementById("settings-pin-set-btn");
|
|
118
|
-
var pinRemoveBtn = document.getElementById("settings-pin-remove-btn");
|
|
119
|
-
var pinSaveBtn = document.getElementById("settings-pin-save-btn");
|
|
120
|
-
var pinCancelBtn = document.getElementById("settings-pin-cancel-btn");
|
|
121
|
-
var pinInput = document.getElementById("settings-pin-input");
|
|
122
|
-
|
|
123
|
-
if (pinSetBtn) pinSetBtn.addEventListener("click", function () { showPinForm(); });
|
|
124
|
-
if (pinRemoveBtn) pinRemoveBtn.addEventListener("click", function () {
|
|
125
|
-
var ws = ctx.ws;
|
|
126
|
-
if (ws && ws.readyState === 1) {
|
|
127
|
-
ws.send(JSON.stringify({ type: "set_pin", pin: null }));
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
if (pinSaveBtn) pinSaveBtn.addEventListener("click", function () { submitPin(); });
|
|
131
|
-
if (pinCancelBtn) pinCancelBtn.addEventListener("click", function () { hidePinForm(); });
|
|
132
|
-
if (pinInput) pinInput.addEventListener("keydown", function (e) {
|
|
133
|
-
if (e.key === "Enter") { e.preventDefault(); submitPin(); }
|
|
134
|
-
if (e.key === "Escape") { e.preventDefault(); hidePinForm(); }
|
|
10
|
+
import { initServerControls } from './server-settings-controls.js';
|
|
11
|
+
import { initServerEnvironment, loadSharedEnv } from './server-settings-environment.js';
|
|
12
|
+
import { renderSettingsNavigation } from './server-settings-navigation.js';
|
|
13
|
+
import { hasSettingsDrafts, confirmSettingsAction, trapSettingsFocus } from './server-settings-state.js';
|
|
14
|
+
import { enhanceSettingsSelects } from './settings-select.js';
|
|
15
|
+
export { updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleServerSettingResult } from './server-settings-controls.js';
|
|
16
|
+
export { handleSharedEnv, handleSharedEnvSaved } from './server-settings-environment.js';
|
|
17
|
+
|
|
18
|
+
function isolateSettings(active) {
|
|
19
|
+
if (active) {
|
|
20
|
+
var entries = Array.from(document.body.children).filter(function (el) { return el !== root() && el.id !== 'confirm-modal' && el.tagName !== 'SCRIPT' && el.tagName !== 'STYLE'; }).map(function (el) { var entry = { el: el, inert: el.inert, hidden: el.getAttribute('aria-hidden') }; el.inert = true; el.setAttribute('aria-hidden', 'true'); return entry; });
|
|
21
|
+
store.set({ serverSettingsInert: entries });
|
|
22
|
+
} else {
|
|
23
|
+
(store.get('serverSettingsInert') || []).forEach(function (entry) { entry.el.inert = entry.inert; if (entry.hidden === null) entry.el.removeAttribute('aria-hidden'); else entry.el.setAttribute('aria-hidden', entry.hidden); });
|
|
24
|
+
store.set({ serverSettingsInert: [] });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function root() { return document.getElementById('server-settings'); }
|
|
28
|
+
export function isSettingsOpen() { return root() && !root().classList.contains('hidden'); }
|
|
29
|
+
function send(type) { var ws = getWs(); if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: type })); }
|
|
30
|
+
|
|
31
|
+
function selectPage(id, focus) {
|
|
32
|
+
if (!store.get('serverSettingsAuthorized')) return;
|
|
33
|
+
if (id === 'admin-invites') id = 'admin-users';
|
|
34
|
+
var nav = document.getElementById('settings-page-nav');
|
|
35
|
+
if (!nav.querySelector('[data-section="' + id + '"]')) id = 'overview';
|
|
36
|
+
store.set({ serverSettingsPage: id });
|
|
37
|
+
root().querySelectorAll('.server-settings-section').forEach(function (page) { page.classList.toggle('active', page.dataset.section === id); });
|
|
38
|
+
nav.querySelectorAll('button').forEach(function (button) {
|
|
39
|
+
button.classList.toggle('active', button.dataset.section === id);
|
|
40
|
+
if (button.dataset.section === id) button.setAttribute('aria-current', 'page'); else button.removeAttribute('aria-current');
|
|
135
41
|
});
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
var ws = ctx.ws;
|
|
144
|
-
if (ws && ws.readyState === 1) {
|
|
145
|
-
ws.send(JSON.stringify({ type: "set_keep_awake", value: this.checked }));
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Inherit supplementary groups toggle (OS-users mode)
|
|
151
|
-
var inheritGroupsToggle = document.getElementById("settings-inherit-groups");
|
|
152
|
-
if (inheritGroupsToggle) {
|
|
153
|
-
inheritGroupsToggle.addEventListener("change", function () {
|
|
154
|
-
var ws = ctx.ws;
|
|
155
|
-
if (ws && ws.readyState === 1) {
|
|
156
|
-
ws.send(JSON.stringify({ type: "set_inherit_groups", value: this.checked }));
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Image retention select
|
|
162
|
-
var imageRetentionSelect = document.getElementById("settings-image-retention");
|
|
163
|
-
if (imageRetentionSelect) {
|
|
164
|
-
imageRetentionSelect.addEventListener("change", function () {
|
|
165
|
-
var ws = ctx.ws;
|
|
166
|
-
if (ws && ws.readyState === 1) {
|
|
167
|
-
ws.send(JSON.stringify({ type: "set_image_retention", days: parseInt(this.value, 10) }));
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Global CLAUDE.md: save button
|
|
173
|
-
var ssClaudeMdSave = document.getElementById("ss-claudemd-save");
|
|
174
|
-
if (ssClaudeMdSave) {
|
|
175
|
-
ssClaudeMdSave.addEventListener("click", function () { saveGlobalClaudeMd(); });
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Shared environment: add button
|
|
179
|
-
var ssEnvAddBtn = document.getElementById("ss-env-add-btn");
|
|
180
|
-
if (ssEnvAddBtn) {
|
|
181
|
-
ssEnvAddBtn.addEventListener("click", function () {
|
|
182
|
-
addSharedEnvRow("", "", true);
|
|
183
|
-
autoSaveSharedEnv();
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Restart server
|
|
188
|
-
var restartBtn = document.getElementById("settings-restart-btn");
|
|
189
|
-
if (restartBtn) {
|
|
190
|
-
restartBtn.addEventListener("click", function () {
|
|
191
|
-
var ws = ctx.ws;
|
|
192
|
-
if (ws && ws.readyState === 1) {
|
|
193
|
-
restartBtn.disabled = true;
|
|
194
|
-
restartBtn.textContent = "Restarting...";
|
|
195
|
-
ws.send(JSON.stringify({ type: "restart_server" }));
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Shutdown server
|
|
201
|
-
var shutdownInput = document.getElementById("settings-shutdown-input");
|
|
202
|
-
var shutdownBtn = document.getElementById("settings-shutdown-btn");
|
|
203
|
-
|
|
204
|
-
if (shutdownInput && shutdownBtn) {
|
|
205
|
-
shutdownInput.addEventListener("input", function () {
|
|
206
|
-
var val = this.value.trim().toLowerCase();
|
|
207
|
-
shutdownBtn.disabled = val !== "shutdown";
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
shutdownInput.addEventListener("keydown", function (e) {
|
|
211
|
-
if (e.key === "Enter") {
|
|
212
|
-
e.preventDefault();
|
|
213
|
-
if (!shutdownBtn.disabled) shutdownBtn.click();
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
shutdownBtn.addEventListener("click", function () {
|
|
218
|
-
var val = shutdownInput.value.trim().toLowerCase();
|
|
219
|
-
if (val !== "shutdown") return;
|
|
220
|
-
var ws = ctx.ws;
|
|
221
|
-
if (ws && ws.readyState === 1) {
|
|
222
|
-
shutdownBtn.disabled = true;
|
|
223
|
-
shutdownBtn.textContent = "Shutting down...";
|
|
224
|
-
shutdownInput.disabled = true;
|
|
225
|
-
ws.send(JSON.stringify({ type: "shutdown_server" }));
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function switchSection(sectionName) {
|
|
232
|
-
for (var i = 0; i < navItems.length; i++) {
|
|
233
|
-
var isActive = navItems[i].dataset.section === sectionName;
|
|
234
|
-
navItems[i].classList.toggle("active", isActive);
|
|
235
|
-
}
|
|
236
|
-
// Sync mobile dropdown
|
|
237
|
-
var navDropdown = document.getElementById("settings-nav-dropdown");
|
|
238
|
-
if (navDropdown && navDropdown.value !== sectionName) {
|
|
239
|
-
navDropdown.value = sectionName;
|
|
42
|
+
document.getElementById('settings-nav-dropdown').value = id;
|
|
43
|
+
if (id === 'network') loadNetworkSettings();
|
|
44
|
+
if (id === 'storage') loadStorageUsage();
|
|
45
|
+
if (id === 'environment') loadSharedEnv();
|
|
46
|
+
if (id.indexOf('admin-') === 0) {
|
|
47
|
+
var body = document.getElementById(id + '-body');
|
|
48
|
+
if (id !== 'admin-smtp' || !(store.get('serverSettingsDrafts') || {}).smtp) loadAdminSection(id, body);
|
|
240
49
|
}
|
|
241
|
-
|
|
242
|
-
var
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (sectionName === "network") loadNetworkSettings();
|
|
247
|
-
|
|
248
|
-
// Lazy-load section data
|
|
249
|
-
if (sectionName === "claudemd") loadGlobalClaudeMd();
|
|
250
|
-
if (sectionName === "environment") loadSharedEnv();
|
|
251
|
-
if (sectionName === "admin-users" || sectionName === "admin-invites" || sectionName === "admin-projects" || sectionName === "admin-smtp") {
|
|
252
|
-
var adminBody = document.getElementById(sectionName + "-body");
|
|
253
|
-
if (adminBody) loadAdminSection(sectionName, adminBody);
|
|
50
|
+
if (focus) {
|
|
51
|
+
var heading = root().querySelector('.server-settings-section.active h2');
|
|
52
|
+
heading.tabIndex = -1; heading.focus();
|
|
254
53
|
}
|
|
255
54
|
}
|
|
256
55
|
|
|
257
|
-
function openSettings() {
|
|
56
|
+
async function openSettings() {
|
|
57
|
+
if (isSettingsOpen()) return;
|
|
258
58
|
closeFileViewer();
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
59
|
+
isolateSettings(true);
|
|
60
|
+
store.set({ serverSettingsReturnFocus: document.activeElement });
|
|
61
|
+
root().classList.remove('hidden');
|
|
62
|
+
document.getElementById('server-settings-btn').classList.add('active');
|
|
63
|
+
document.getElementById('server-settings-close').focus();
|
|
64
|
+
var status = document.getElementById('settings-access-status');
|
|
65
|
+
status.textContent = 'Checking administrator access…';
|
|
66
|
+
store.set({ serverSettingsAuthorized: false });
|
|
67
|
+
root().querySelectorAll('.server-settings-section').forEach(function (page) { page.classList.remove('active'); });
|
|
68
|
+
try {
|
|
69
|
+
var response = await fetch('/api/me', { credentials: 'same-origin' });
|
|
70
|
+
if (!response.ok) throw new Error('Could not verify access. Close and reopen Settings to retry.');
|
|
71
|
+
var data = await response.json();
|
|
72
|
+
if (data.multiUser && (!data.user || data.user.role !== 'admin')) {
|
|
73
|
+
status.textContent = 'Server settings require an administrator. Your personal preferences are available below.';
|
|
74
|
+
document.getElementById('settings-page-nav').replaceChildren();
|
|
75
|
+
document.getElementById('settings-nav-dropdown').replaceChildren();
|
|
76
|
+
return;
|
|
273
77
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
function
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (btn) { btn.disabled = true; btn.textContent = "Shutdown"; }
|
|
294
|
-
if (errorEl) errorEl.classList.add("hidden");
|
|
78
|
+
await checkAdminAccess();
|
|
79
|
+
if (!isSettingsOpen()) return;
|
|
80
|
+
store.set({ serverSettingsAuthorized: true });
|
|
81
|
+
status.textContent = '';
|
|
82
|
+
renderSettingsNavigation(!!data.multiUser, selectPage);
|
|
83
|
+
selectPage(store.get('serverSettingsPage') || 'overview', false);
|
|
84
|
+
if (!hasSettingsDrafts()) send('get_daemon_config');
|
|
85
|
+
setTimeout(function () {
|
|
86
|
+
if (isSettingsOpen() && !store.get('serverSettingsConfigLoaded')) status.textContent = 'Server configuration has not loaded. Reconnect, then reopen Settings to retry.';
|
|
87
|
+
}, 12000);
|
|
88
|
+
send('process_stats');
|
|
89
|
+
clearInterval(store.get('serverSettingsStatsTimer'));
|
|
90
|
+
store.set({ serverSettingsStatsTimer: setInterval(function () { send('process_stats'); }, 5000) });
|
|
91
|
+
document.getElementById('settings-project-slug').textContent = store.get('currentSlug') || '(none)';
|
|
92
|
+
document.getElementById('settings-ws-path').textContent = store.get('wsPath') || '/ws';
|
|
93
|
+
var pill = document.getElementById('skip-perms-pill');
|
|
94
|
+
document.getElementById('settings-skip-perms').textContent = pill && !pill.classList.contains('hidden') ? 'Skip' : 'Permission checks';
|
|
95
|
+
enhanceSettingsSelects(root()); refreshIcons(root());
|
|
96
|
+
} catch (error) { status.textContent = error.message; }
|
|
295
97
|
}
|
|
296
98
|
|
|
297
99
|
function closeSettings() {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
// Version is set from WebSocket "info" message in app.js
|
|
328
|
-
if (versionEl && !versionEl.textContent) versionEl.textContent = "-";
|
|
329
|
-
|
|
330
|
-
if (slugEl) slugEl.textContent = ctx.currentSlug || "(default)";
|
|
331
|
-
if (wsPathEl) wsPathEl.textContent = ctx.wsPath || "/ws";
|
|
332
|
-
|
|
333
|
-
// Skip permissions
|
|
334
|
-
var spBanner = document.getElementById("skip-perms-pill");
|
|
335
|
-
if (skipPermsEl) {
|
|
336
|
-
var isSkip = spBanner && !spBanner.classList.contains("hidden");
|
|
337
|
-
skipPermsEl.textContent = isSkip ? "Enabled" : "Disabled";
|
|
338
|
-
skipPermsEl.classList.toggle("settings-badge-on", isSkip);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Sync notification toggles
|
|
342
|
-
syncNotifToggles();
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function syncNotifToggles() {
|
|
347
|
-
var pairs = [
|
|
348
|
-
["notif-toggle-alert", "settings-notif-alert"],
|
|
349
|
-
["notif-toggle-sound", "settings-notif-sound"],
|
|
350
|
-
["notif-toggle-push", "settings-notif-push"],
|
|
351
|
-
];
|
|
352
|
-
for (var i = 0; i < pairs.length; i++) {
|
|
353
|
-
var src = document.getElementById(pairs[i][0]);
|
|
354
|
-
var dst = document.getElementById(pairs[i][1]);
|
|
355
|
-
if (src && dst) dst.checked = src.checked;
|
|
356
|
-
}
|
|
100
|
+
function close() {
|
|
101
|
+
isolateSettings(false);
|
|
102
|
+
root().classList.add('hidden');
|
|
103
|
+
document.getElementById('server-settings-btn').classList.remove('active');
|
|
104
|
+
clearInterval(store.get('serverSettingsStatsTimer'));
|
|
105
|
+
var previous = store.get('serverSettingsReturnFocus');
|
|
106
|
+
if (previous && previous.isConnected) previous.focus();
|
|
107
|
+
}
|
|
108
|
+
if (hasSettingsDrafts()) confirmSettingsAction('You have unsaved changes. Closing keeps these drafts in this tab, but reloading the page will lose them.', 'Close with drafts', close);
|
|
109
|
+
else close();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function initServerSettings() {
|
|
113
|
+
if (!root()) return;
|
|
114
|
+
initNetworkSettings(); initServerControls(); initServerEnvironment(); initStorageUsage();
|
|
115
|
+
document.getElementById('server-settings-btn').addEventListener('click', openSettings);
|
|
116
|
+
document.getElementById('server-settings-close').onclick = closeSettings;
|
|
117
|
+
document.getElementById('settings-personal-link').onclick = function () {
|
|
118
|
+
function openPersonal() { isolateSettings(false); document.getElementById('server-settings-btn').classList.remove('active'); root().classList.add('hidden'); clearInterval(store.get('serverSettingsStatsTimer')); openUserSettings('us-notifications'); }
|
|
119
|
+
if (hasSettingsDrafts()) confirmSettingsAction('Your server settings drafts will remain in this tab while you visit Personal settings.', 'Open Personal settings', openPersonal);
|
|
120
|
+
else openPersonal();
|
|
121
|
+
};
|
|
122
|
+
document.addEventListener('keydown', function (event) {
|
|
123
|
+
if (!isSettingsOpen() || document.querySelector('.admin-modal-overlay, #settings-confirm, .confirm-above-settings')) return;
|
|
124
|
+
if (event.key === 'Escape') { event.preventDefault(); closeSettings(); }
|
|
125
|
+
trapSettingsFocus(event, root());
|
|
126
|
+
});
|
|
127
|
+
new MutationObserver(function () { enhanceSettingsSelects(root()); }).observe(root(), { childList: true, subtree: true });
|
|
357
128
|
}
|
|
358
129
|
|
|
359
130
|
export function updateSettingsStats(data) {
|
|
360
131
|
if (!isSettingsOpen()) return;
|
|
361
|
-
var
|
|
362
|
-
var
|
|
363
|
-
var rss = document.getElementById("settings-status-rss");
|
|
364
|
-
var sessions = document.getElementById("settings-status-sessions");
|
|
365
|
-
var clients = document.getElementById("settings-status-clients");
|
|
366
|
-
|
|
367
|
-
if (pid) pid.textContent = String(data.pid);
|
|
368
|
-
if (uptime) uptime.textContent = formatUptime(data.uptime);
|
|
369
|
-
if (rss) rss.textContent = formatBytes(data.memory.rss);
|
|
370
|
-
if (sessions) sessions.textContent = String(data.sessions);
|
|
371
|
-
if (clients) clients.textContent = String(data.clients);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// --- Daemon config ---
|
|
375
|
-
function requestDaemonConfig() {
|
|
376
|
-
var ws = ctx.ws;
|
|
377
|
-
if (ws && ws.readyState === 1) {
|
|
378
|
-
ws.send(JSON.stringify({ type: "get_daemon_config" }));
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
export function updateDaemonConfig(config) {
|
|
383
|
-
// Nav header: show hostname (strip .local suffix, lowercase)
|
|
384
|
-
var nameEl = document.getElementById("settings-server-name");
|
|
385
|
-
if (nameEl && config.hostname) {
|
|
386
|
-
var displayHost = config.hostname.replace(/\.local$/i, "").toLowerCase();
|
|
387
|
-
nameEl.textContent = displayHost;
|
|
388
|
-
nameEl.title = config.hostname;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Host
|
|
392
|
-
var hostnameEl = document.getElementById("settings-hostname");
|
|
393
|
-
var lanIpEl = document.getElementById("settings-lan-ip");
|
|
394
|
-
if (hostnameEl) hostnameEl.textContent = config.hostname || "-";
|
|
395
|
-
if (lanIpEl) lanIpEl.textContent = config.lanIp || "";
|
|
396
|
-
|
|
397
|
-
// Port
|
|
398
|
-
var portEl = document.getElementById("settings-port");
|
|
399
|
-
if (portEl) portEl.textContent = String(config.port || "-");
|
|
400
|
-
|
|
401
|
-
// TLS
|
|
402
|
-
var tlsEl = document.getElementById("settings-tls");
|
|
403
|
-
if (tlsEl) {
|
|
404
|
-
tlsEl.textContent = config.tls ? "Enabled" : "Disabled";
|
|
405
|
-
tlsEl.classList.toggle("settings-badge-green", !!config.tls);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Debug
|
|
409
|
-
var debugEl = document.getElementById("settings-debug");
|
|
410
|
-
if (debugEl) {
|
|
411
|
-
debugEl.textContent = config.debug ? "Enabled" : "Disabled";
|
|
412
|
-
debugEl.classList.toggle("settings-badge-on", !!config.debug);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// PIN status
|
|
416
|
-
updatePinStatus(!!config.pinEnabled);
|
|
417
|
-
|
|
418
|
-
// Auto-continue on rate limit
|
|
419
|
-
// Auto-continue is now per-user (User Settings > Behavior)
|
|
420
|
-
|
|
421
|
-
// Keep awake
|
|
422
|
-
var keepAwakeToggle = document.getElementById("settings-keep-awake");
|
|
423
|
-
if (keepAwakeToggle) keepAwakeToggle.checked = !!config.keepAwake;
|
|
424
|
-
|
|
425
|
-
// Inherit supplementary groups (only relevant in OS-users mode)
|
|
426
|
-
var inheritGroupsToggle = document.getElementById("settings-inherit-groups");
|
|
427
|
-
if (inheritGroupsToggle) inheritGroupsToggle.checked = config.inheritGroups !== false;
|
|
428
|
-
|
|
429
|
-
// Image retention
|
|
430
|
-
var imageRetentionSelect = document.getElementById("settings-image-retention");
|
|
431
|
-
if (imageRetentionSelect && config.imageRetentionDays !== undefined) {
|
|
432
|
-
imageRetentionSelect.value = String(config.imageRetentionDays);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Early Access toggle
|
|
436
|
-
var channelToggle = document.getElementById("settings-update-channel");
|
|
437
|
-
if (channelToggle) {
|
|
438
|
-
channelToggle.checked = (config.updateChannel === "beta");
|
|
439
|
-
channelToggle.onchange = function () {
|
|
440
|
-
var channel = channelToggle.checked ? "beta" : "stable";
|
|
441
|
-
if (ctx.ws && ctx.ws.readyState === 1) {
|
|
442
|
-
ctx.ws.send(JSON.stringify({ type: "set_update_channel", channel: channel }));
|
|
443
|
-
// Auto-trigger update check after channel change
|
|
444
|
-
setTimeout(function () {
|
|
445
|
-
ctx.ws.send(JSON.stringify({ type: "check_update" }));
|
|
446
|
-
}, 200);
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Show keep awake section/nav only on macOS
|
|
452
|
-
var keepAwakeSection = document.getElementById("settings-keep-awake-section");
|
|
453
|
-
var keepAwakeNav = document.getElementById("settings-keep-awake-nav");
|
|
454
|
-
var keepAwakeOpt = document.getElementById("settings-keep-awake-opt");
|
|
455
|
-
if (config.platform === "darwin") {
|
|
456
|
-
if (keepAwakeSection) keepAwakeSection.classList.remove("hidden");
|
|
457
|
-
if (keepAwakeNav) keepAwakeNav.classList.remove("hidden");
|
|
458
|
-
if (keepAwakeOpt) keepAwakeOpt.classList.remove("hidden");
|
|
459
|
-
} else {
|
|
460
|
-
if (keepAwakeSection) keepAwakeSection.classList.add("hidden");
|
|
461
|
-
if (keepAwakeNav) keepAwakeNav.classList.add("hidden");
|
|
462
|
-
if (keepAwakeOpt) keepAwakeOpt.classList.add("hidden");
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Show inherit-groups section/nav only in OS-users mode
|
|
466
|
-
var inheritGroupsSection = document.getElementById("settings-inherit-groups-section");
|
|
467
|
-
var inheritGroupsNav = document.getElementById("settings-inherit-groups-nav");
|
|
468
|
-
var inheritGroupsOpt = document.getElementById("settings-inherit-groups-opt");
|
|
469
|
-
if (config.osUsers) {
|
|
470
|
-
if (inheritGroupsSection) inheritGroupsSection.classList.remove("hidden");
|
|
471
|
-
if (inheritGroupsNav) inheritGroupsNav.classList.remove("hidden");
|
|
472
|
-
if (inheritGroupsOpt) inheritGroupsOpt.classList.remove("hidden");
|
|
473
|
-
} else {
|
|
474
|
-
if (inheritGroupsSection) inheritGroupsSection.classList.add("hidden");
|
|
475
|
-
if (inheritGroupsNav) inheritGroupsNav.classList.add("hidden");
|
|
476
|
-
if (inheritGroupsOpt) inheritGroupsOpt.classList.add("hidden");
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
export function handleSetPinResult(msg) {
|
|
481
|
-
if (msg.ok) {
|
|
482
|
-
updatePinStatus(!!msg.pinEnabled);
|
|
483
|
-
hidePinForm();
|
|
484
|
-
showToast(msg.pinEnabled ? "PIN set successfully" : "PIN removed");
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
export function handleKeepAwakeChanged(msg) {
|
|
489
|
-
var keepAwakeToggle = document.getElementById("settings-keep-awake");
|
|
490
|
-
if (keepAwakeToggle) keepAwakeToggle.checked = !!msg.keepAwake;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
export function handleInheritGroupsChanged(msg) {
|
|
494
|
-
var inheritGroupsToggle = document.getElementById("settings-inherit-groups");
|
|
495
|
-
if (inheritGroupsToggle) inheritGroupsToggle.checked = msg.inheritGroups !== false;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
export function handleAutoContinueChanged(msg) {
|
|
499
|
-
// Auto-continue is now per-user; server broadcast no longer updates UI
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
export function handleRestartResult(msg) {
|
|
503
|
-
var restartBtn = document.getElementById("settings-restart-btn");
|
|
504
|
-
var errorEl = document.getElementById("settings-restart-error");
|
|
505
|
-
|
|
506
|
-
if (msg.ok) {
|
|
507
|
-
if (restartBtn) restartBtn.textContent = "Server restarting...";
|
|
508
|
-
showToast("Server is restarting...");
|
|
509
|
-
} else {
|
|
510
|
-
if (restartBtn) {
|
|
511
|
-
restartBtn.textContent = "Restart";
|
|
512
|
-
restartBtn.disabled = false;
|
|
513
|
-
}
|
|
514
|
-
if (errorEl) {
|
|
515
|
-
errorEl.textContent = msg.error || "Restart failed";
|
|
516
|
-
errorEl.classList.remove("hidden");
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
export function handleShutdownResult(msg) {
|
|
522
|
-
var shutdownInput = document.getElementById("settings-shutdown-input");
|
|
523
|
-
var shutdownBtn = document.getElementById("settings-shutdown-btn");
|
|
524
|
-
var errorEl = document.getElementById("settings-shutdown-error");
|
|
525
|
-
|
|
526
|
-
if (msg.ok) {
|
|
527
|
-
if (shutdownBtn) shutdownBtn.textContent = "Server stopped";
|
|
528
|
-
showToast("Server is shutting down...");
|
|
529
|
-
} else {
|
|
530
|
-
if (shutdownBtn) {
|
|
531
|
-
shutdownBtn.textContent = "Shutdown";
|
|
532
|
-
shutdownBtn.disabled = false;
|
|
533
|
-
}
|
|
534
|
-
if (shutdownInput) shutdownInput.disabled = false;
|
|
535
|
-
if (errorEl) {
|
|
536
|
-
errorEl.textContent = msg.error || "Shutdown failed";
|
|
537
|
-
errorEl.classList.remove("hidden");
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// --- PIN form management ---
|
|
543
|
-
function showPinForm() {
|
|
544
|
-
var form = document.getElementById("settings-pin-form");
|
|
545
|
-
var input = document.getElementById("settings-pin-input");
|
|
546
|
-
var errorEl = document.getElementById("settings-pin-error");
|
|
547
|
-
if (form) form.classList.remove("hidden");
|
|
548
|
-
if (errorEl) errorEl.classList.add("hidden");
|
|
549
|
-
if (input) { input.value = ""; input.focus(); }
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function hidePinForm() {
|
|
553
|
-
var form = document.getElementById("settings-pin-form");
|
|
554
|
-
var input = document.getElementById("settings-pin-input");
|
|
555
|
-
var errorEl = document.getElementById("settings-pin-error");
|
|
556
|
-
if (form) form.classList.add("hidden");
|
|
557
|
-
if (input) input.value = "";
|
|
558
|
-
if (errorEl) errorEl.classList.add("hidden");
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
function submitPin() {
|
|
562
|
-
var input = document.getElementById("settings-pin-input");
|
|
563
|
-
var errorEl = document.getElementById("settings-pin-error");
|
|
564
|
-
if (!input) return;
|
|
565
|
-
var pin = input.value.trim();
|
|
566
|
-
if (!/^\d{6}$/.test(pin)) {
|
|
567
|
-
if (errorEl) errorEl.classList.remove("hidden");
|
|
568
|
-
input.focus();
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
if (errorEl) errorEl.classList.add("hidden");
|
|
572
|
-
var ws = ctx.ws;
|
|
573
|
-
if (ws && ws.readyState === 1) {
|
|
574
|
-
ws.send(JSON.stringify({ type: "set_pin", pin: pin }));
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function updatePinStatus(enabled) {
|
|
579
|
-
var statusEl = document.getElementById("settings-pin-status");
|
|
580
|
-
var setBtn = document.getElementById("settings-pin-set-btn");
|
|
581
|
-
var removeBtn = document.getElementById("settings-pin-remove-btn");
|
|
582
|
-
var actionLabel = document.getElementById("settings-pin-action-label");
|
|
583
|
-
|
|
584
|
-
if (statusEl) {
|
|
585
|
-
statusEl.textContent = enabled ? "Enabled" : "Disabled";
|
|
586
|
-
statusEl.classList.toggle("settings-badge-green", enabled);
|
|
587
|
-
}
|
|
588
|
-
if (setBtn) setBtn.textContent = enabled ? "Change PIN" : "Set PIN";
|
|
589
|
-
if (removeBtn) removeBtn.classList.toggle("hidden", !enabled);
|
|
590
|
-
if (actionLabel) actionLabel.textContent = enabled ? "Change PIN" : "Set PIN";
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// ===== Global CLAUDE.md =====
|
|
594
|
-
function loadGlobalClaudeMd() {
|
|
595
|
-
var editor = document.getElementById("ss-claudemd-editor");
|
|
596
|
-
var status = document.getElementById("ss-claudemd-status");
|
|
597
|
-
var saveStatus = document.getElementById("ss-claudemd-save-status");
|
|
598
|
-
if (saveStatus) saveStatus.textContent = "";
|
|
599
|
-
if (status) status.textContent = "Loading...";
|
|
600
|
-
|
|
601
|
-
var ws = ctx.ws;
|
|
602
|
-
if (ws && ws.readyState === 1) {
|
|
603
|
-
ws.send(JSON.stringify({ type: "read_global_claude_md" }));
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
export function handleGlobalClaudeMdRead(msg) {
|
|
608
|
-
var editor = document.getElementById("ss-claudemd-editor");
|
|
609
|
-
var status = document.getElementById("ss-claudemd-status");
|
|
610
|
-
if (!editor) return;
|
|
611
|
-
|
|
612
|
-
if (msg.error) {
|
|
613
|
-
editor.value = "";
|
|
614
|
-
if (status) status.textContent = "No global CLAUDE.md found. Save to create one.";
|
|
615
|
-
} else {
|
|
616
|
-
editor.value = msg.content || "";
|
|
617
|
-
if (status) status.textContent = "";
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
function saveGlobalClaudeMd() {
|
|
622
|
-
var editor = document.getElementById("ss-claudemd-editor");
|
|
623
|
-
var saveStatus = document.getElementById("ss-claudemd-save-status");
|
|
624
|
-
if (!editor) return;
|
|
625
|
-
|
|
626
|
-
var ws = ctx.ws;
|
|
627
|
-
if (ws && ws.readyState === 1) {
|
|
628
|
-
ws.send(JSON.stringify({ type: "write_global_claude_md", content: editor.value }));
|
|
629
|
-
if (saveStatus) saveStatus.textContent = "Saving...";
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
export function handleGlobalClaudeMdWrite(msg) {
|
|
634
|
-
var saveStatus = document.getElementById("ss-claudemd-save-status");
|
|
635
|
-
if (!saveStatus) return;
|
|
636
|
-
if (msg.ok) {
|
|
637
|
-
saveStatus.textContent = "Saved";
|
|
638
|
-
setTimeout(function () { saveStatus.textContent = ""; }, 2000);
|
|
639
|
-
} else {
|
|
640
|
-
saveStatus.textContent = "Error: " + (msg.error || "Failed to save");
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// ===== Shared Environment Variables =====
|
|
645
|
-
var sharedEnvSaveTimer = null;
|
|
646
|
-
var sharedEnvStatusTimer = null;
|
|
647
|
-
|
|
648
|
-
function showSharedEnvSaveStatus(text, duration) {
|
|
649
|
-
var saveStatus = document.getElementById("ss-env-save-status");
|
|
650
|
-
if (!saveStatus) return;
|
|
651
|
-
if (sharedEnvStatusTimer) clearTimeout(sharedEnvStatusTimer);
|
|
652
|
-
saveStatus.textContent = text;
|
|
653
|
-
sharedEnvStatusTimer = setTimeout(function () { saveStatus.textContent = ""; }, duration);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
function loadSharedEnv() {
|
|
657
|
-
var saveStatus = document.getElementById("ss-env-save-status");
|
|
658
|
-
if (saveStatus) saveStatus.textContent = "";
|
|
659
|
-
|
|
660
|
-
var ws = ctx.ws;
|
|
661
|
-
if (ws && ws.readyState === 1) {
|
|
662
|
-
ws.send(JSON.stringify({ type: "get_shared_env" }));
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
export function handleSharedEnv(msg) {
|
|
667
|
-
var list = document.getElementById("ss-env-list");
|
|
668
|
-
if (!list) return;
|
|
669
|
-
list.innerHTML = "";
|
|
670
|
-
|
|
671
|
-
var pairs = parseEnvString(msg.envrc || "");
|
|
672
|
-
for (var i = 0; i < pairs.length; i++) {
|
|
673
|
-
addSharedEnvRow(pairs[i].key, pairs[i].value, false);
|
|
674
|
-
}
|
|
675
|
-
refreshIcons();
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
export function handleSharedEnvSaved(msg) {
|
|
679
|
-
if (msg.ok) {
|
|
680
|
-
showSharedEnvSaveStatus(msg.timing || "Saved", 5000);
|
|
681
|
-
} else {
|
|
682
|
-
showSharedEnvSaveStatus("Error: " + (msg.error || "Failed to save"), 5000);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
function buildSharedEnvString() {
|
|
687
|
-
var list = document.getElementById("ss-env-list");
|
|
688
|
-
if (!list) return "";
|
|
689
|
-
var rows = list.querySelectorAll(".ps-env-row");
|
|
690
|
-
var lines = [];
|
|
691
|
-
for (var i = 0; i < rows.length; i++) {
|
|
692
|
-
var keyInput = rows[i].querySelector(".ps-env-key");
|
|
693
|
-
var valInput = rows[i].querySelector(".ps-env-val");
|
|
694
|
-
var key = keyInput ? keyInput.value.trim() : "";
|
|
695
|
-
var val = valInput ? valInput.value : "";
|
|
696
|
-
if (key) lines.push("export " + key + "=" + serializeEnvValue(val));
|
|
697
|
-
}
|
|
698
|
-
return lines.join("\n");
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
function addSharedEnvRow(key, value, focus) {
|
|
702
|
-
var list = document.getElementById("ss-env-list");
|
|
703
|
-
if (!list) return;
|
|
704
|
-
|
|
705
|
-
var row = document.createElement("div");
|
|
706
|
-
row.className = "ps-env-row";
|
|
707
|
-
|
|
708
|
-
var keyInput = document.createElement("input");
|
|
709
|
-
keyInput.type = "text";
|
|
710
|
-
keyInput.className = "ps-env-key";
|
|
711
|
-
keyInput.placeholder = "KEY";
|
|
712
|
-
keyInput.value = key;
|
|
713
|
-
keyInput.spellcheck = false;
|
|
714
|
-
keyInput.autocomplete = "off";
|
|
715
|
-
|
|
716
|
-
var valInput = document.createElement("input");
|
|
717
|
-
valInput.type = "text";
|
|
718
|
-
valInput.className = "ps-env-val";
|
|
719
|
-
valInput.placeholder = "value";
|
|
720
|
-
valInput.value = value;
|
|
721
|
-
valInput.spellcheck = false;
|
|
722
|
-
valInput.autocomplete = "off";
|
|
723
|
-
|
|
724
|
-
var delBtn = document.createElement("button");
|
|
725
|
-
delBtn.className = "ps-env-del";
|
|
726
|
-
delBtn.title = "Remove";
|
|
727
|
-
delBtn.innerHTML = '<i data-lucide="x"></i>';
|
|
728
|
-
|
|
729
|
-
delBtn.addEventListener("click", function () {
|
|
730
|
-
row.remove();
|
|
731
|
-
autoSaveSharedEnv();
|
|
732
|
-
});
|
|
733
|
-
|
|
734
|
-
keyInput.addEventListener("input", function () { autoSaveSharedEnv(); });
|
|
735
|
-
valInput.addEventListener("input", function () { autoSaveSharedEnv(); });
|
|
736
|
-
|
|
737
|
-
// Paste detection
|
|
738
|
-
keyInput.addEventListener("paste", function (e) {
|
|
739
|
-
var text = (e.clipboardData || window.clipboardData).getData("text");
|
|
740
|
-
if (text && looksLikeEnv(text)) {
|
|
741
|
-
e.preventDefault();
|
|
742
|
-
var pairs = parseEnvString(text);
|
|
743
|
-
if (pairs.length > 0) {
|
|
744
|
-
keyInput.value = pairs[0].key;
|
|
745
|
-
valInput.value = pairs[0].value;
|
|
746
|
-
for (var p = 1; p < pairs.length; p++) {
|
|
747
|
-
addSharedEnvRow(pairs[p].key, pairs[p].value, false);
|
|
748
|
-
}
|
|
749
|
-
autoSaveSharedEnv();
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
valInput.addEventListener("paste", function (e) {
|
|
755
|
-
var text = (e.clipboardData || window.clipboardData).getData("text");
|
|
756
|
-
if (text && text.indexOf("\n") !== -1 && text.indexOf("=") !== -1) {
|
|
757
|
-
e.preventDefault();
|
|
758
|
-
var pairs = parseEnvString(text);
|
|
759
|
-
if (pairs.length > 0) {
|
|
760
|
-
keyInput.value = pairs[0].key;
|
|
761
|
-
valInput.value = pairs[0].value;
|
|
762
|
-
for (var p = 1; p < pairs.length; p++) {
|
|
763
|
-
addSharedEnvRow(pairs[p].key, pairs[p].value, false);
|
|
764
|
-
}
|
|
765
|
-
autoSaveSharedEnv();
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
row.appendChild(keyInput);
|
|
771
|
-
row.appendChild(valInput);
|
|
772
|
-
row.appendChild(delBtn);
|
|
773
|
-
list.appendChild(row);
|
|
774
|
-
refreshIcons();
|
|
775
|
-
|
|
776
|
-
if (focus) keyInput.focus();
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
function autoSaveSharedEnv() {
|
|
780
|
-
if (sharedEnvSaveTimer) clearTimeout(sharedEnvSaveTimer);
|
|
781
|
-
sharedEnvSaveTimer = setTimeout(function () {
|
|
782
|
-
var envrc = buildSharedEnvString();
|
|
783
|
-
var ws = ctx.ws;
|
|
784
|
-
if (ws && ws.readyState === 1) {
|
|
785
|
-
ws.send(JSON.stringify({ type: "set_shared_env", envrc: envrc }));
|
|
786
|
-
showSharedEnvSaveStatus("Saving...", 2000);
|
|
787
|
-
}
|
|
788
|
-
}, 800);
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
function formatBytes(n) {
|
|
792
|
-
if (n >= 1073741824) return (n / 1073741824).toFixed(1) + " GB";
|
|
793
|
-
if (n >= 1048576) return (n / 1048576).toFixed(1) + " MB";
|
|
794
|
-
if (n >= 1024) return (n / 1024).toFixed(1) + " KB";
|
|
795
|
-
return n + " B";
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
function formatUptime(seconds) {
|
|
799
|
-
var d = Math.floor(seconds / 86400);
|
|
800
|
-
var h = Math.floor((seconds % 86400) / 3600);
|
|
801
|
-
var m = Math.floor((seconds % 3600) / 60);
|
|
802
|
-
var s = Math.floor(seconds % 60);
|
|
803
|
-
if (d > 0) return d + "d " + h + "h " + m + "m";
|
|
804
|
-
if (h > 0) return h + "h " + m + "m " + s + "s";
|
|
805
|
-
return m + "m " + s + "s";
|
|
132
|
+
var values = { pid: data.pid, uptime: Math.floor(data.uptime / 3600) + 'h ' + Math.floor(data.uptime % 3600 / 60) + 'm', rss: (data.memory.rss / 1048576).toFixed(1) + ' MB', sessions: data.sessions, clients: data.clients };
|
|
133
|
+
Object.keys(values).forEach(function (key) { var el = document.getElementById('settings-status-' + key); if (el) el.textContent = String(values[key]); });
|
|
806
134
|
}
|