aicodeman 1.4.1 → 1.5.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/README.md +20 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +168 -0
- package/dist/cli.js.map +1 -1
- package/dist/config/multiuser.d.ts +38 -0
- package/dist/config/multiuser.d.ts.map +1 -0
- package/dist/config/multiuser.js +58 -0
- package/dist/config/multiuser.js.map +1 -0
- package/dist/cron/cron-service.d.ts +1 -1
- package/dist/cron/cron-service.d.ts.map +1 -1
- package/dist/cron/cron-service.js +31 -4
- package/dist/cron/cron-service.js.map +1 -1
- package/dist/mux-interface.d.ts +6 -0
- package/dist/mux-interface.d.ts.map +1 -1
- package/dist/plan-orchestrator.d.ts +8 -1
- package/dist/plan-orchestrator.d.ts.map +1 -1
- package/dist/plan-orchestrator.js +20 -1
- package/dist/plan-orchestrator.js.map +1 -1
- package/dist/push-store.d.ts +17 -5
- package/dist/push-store.d.ts.map +1 -1
- package/dist/push-store.js +3 -1
- package/dist/push-store.js.map +1 -1
- package/dist/session-cli-builder.d.ts +1 -1
- package/dist/session-cli-builder.d.ts.map +1 -1
- package/dist/session-cli-builder.js +7 -2
- package/dist/session-cli-builder.js.map +1 -1
- package/dist/session.d.ts +9 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +19 -2
- package/dist/session.js.map +1 -1
- package/dist/tmux-manager.d.ts +15 -1
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +21 -6
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tunnel-manager.d.ts +17 -0
- package/dist/tunnel-manager.d.ts.map +1 -1
- package/dist/tunnel-manager.js +48 -8
- package/dist/tunnel-manager.js.map +1 -1
- package/dist/types/api.d.ts +10 -0
- package/dist/types/api.d.ts.map +1 -1
- package/dist/types/api.js +20 -0
- package/dist/types/api.js.map +1 -1
- package/dist/types/cron.d.ts +2 -0
- package/dist/types/cron.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/session.d.ts +8 -1
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/types/user.d.ts +59 -0
- package/dist/types/user.d.ts.map +1 -0
- package/dist/types/user.js +12 -0
- package/dist/types/user.js.map +1 -0
- package/dist/user-store.d.ts +139 -0
- package/dist/user-store.d.ts.map +1 -0
- package/dist/user-store.js +433 -0
- package/dist/user-store.js.map +1 -0
- package/dist/web/admin-audit.d.ts +17 -0
- package/dist/web/admin-audit.d.ts.map +1 -0
- package/dist/web/admin-audit.js +19 -0
- package/dist/web/admin-audit.js.map +1 -0
- package/dist/web/middleware/auth.d.ts +8 -0
- package/dist/web/middleware/auth.d.ts.map +1 -1
- package/dist/web/middleware/auth.js +250 -51
- package/dist/web/middleware/auth.js.map +1 -1
- package/dist/web/ports/auth-port.d.ts +13 -0
- package/dist/web/ports/auth-port.d.ts.map +1 -1
- package/dist/web/ports/config-port.d.ts +4 -1
- package/dist/web/ports/config-port.d.ts.map +1 -1
- package/dist/web/ports/infra-port.d.ts +4 -1
- package/dist/web/ports/infra-port.d.ts.map +1 -1
- package/dist/web/public/admin-ui.js +260 -0
- package/dist/web/public/admin-ui.js.br +0 -0
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.6c4b9893.js.gz +0 -0
- package/dist/web/public/{constants.48da7f03.js → constants.5ce97313.js} +3 -0
- package/dist/web/public/constants.5ce97313.js.br +0 -0
- package/dist/web/public/{constants.48da7f03.js.gz → constants.5ce97313.js.gz} +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +5 -3
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
- package/dist/web/public/mobile-handlers.b5a667ef.js.gz +0 -0
- package/dist/web/public/mobile.0644e3ac.css.gz +0 -0
- package/dist/web/public/notification-manager.ca9dc40d.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.3c9b0465.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-ui.4851c61a.js.gz +0 -0
- package/dist/web/public/settings-ui.29214cf1.js.gz +0 -0
- package/dist/web/public/styles.9e6cdb9d.css +1 -0
- package/dist/web/public/styles.9e6cdb9d.css.br +0 -0
- package/dist/web/public/styles.9e6cdb9d.css.gz +0 -0
- package/dist/web/public/subagent-windows.69b60c35.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.efda6083.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
- package/dist/web/route-helpers.d.ts +78 -1
- package/dist/web/route-helpers.d.ts.map +1 -1
- package/dist/web/route-helpers.js +172 -3
- package/dist/web/route-helpers.js.map +1 -1
- package/dist/web/routes/admin-routes.d.ts +20 -0
- package/dist/web/routes/admin-routes.d.ts.map +1 -0
- package/dist/web/routes/admin-routes.js +193 -0
- package/dist/web/routes/admin-routes.js.map +1 -0
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +137 -59
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/clipboard-routes.d.ts +2 -2
- package/dist/web/routes/clipboard-routes.d.ts.map +1 -1
- package/dist/web/routes/clipboard-routes.js +10 -0
- package/dist/web/routes/clipboard-routes.js.map +1 -1
- package/dist/web/routes/cron-routes.d.ts.map +1 -1
- package/dist/web/routes/cron-routes.js +57 -9
- package/dist/web/routes/cron-routes.js.map +1 -1
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +26 -20
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/index.d.ts +2 -0
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +2 -0
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/routes/me-routes.d.ts +16 -0
- package/dist/web/routes/me-routes.d.ts.map +1 -0
- package/dist/web/routes/me-routes.js +73 -0
- package/dist/web/routes/me-routes.js.map +1 -0
- package/dist/web/routes/mux-routes.d.ts.map +1 -1
- package/dist/web/routes/mux-routes.js +23 -5
- package/dist/web/routes/mux-routes.js.map +1 -1
- package/dist/web/routes/orchestrator-routes.d.ts.map +1 -1
- package/dist/web/routes/orchestrator-routes.js +43 -11
- package/dist/web/routes/orchestrator-routes.js.map +1 -1
- package/dist/web/routes/plan-routes.d.ts.map +1 -1
- package/dist/web/routes/plan-routes.js +28 -9
- package/dist/web/routes/plan-routes.js.map +1 -1
- package/dist/web/routes/push-routes.d.ts.map +1 -1
- package/dist/web/routes/push-routes.js +4 -0
- package/dist/web/routes/push-routes.js.map +1 -1
- package/dist/web/routes/ralph-routes.d.ts.map +1 -1
- package/dist/web/routes/ralph-routes.js +21 -17
- package/dist/web/routes/ralph-routes.js.map +1 -1
- package/dist/web/routes/respawn-routes.d.ts.map +1 -1
- package/dist/web/routes/respawn-routes.js +23 -14
- package/dist/web/routes/respawn-routes.js.map +1 -1
- package/dist/web/routes/scheduled-routes.d.ts.map +1 -1
- package/dist/web/routes/scheduled-routes.js +22 -6
- package/dist/web/routes/scheduled-routes.js.map +1 -1
- package/dist/web/routes/search-routes.d.ts.map +1 -1
- package/dist/web/routes/search-routes.js +9 -3
- package/dist/web/routes/search-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +188 -61
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +108 -23
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/routes/ws-routes.d.ts.map +1 -1
- package/dist/web/routes/ws-routes.js +11 -0
- package/dist/web/routes/ws-routes.js.map +1 -1
- package/dist/web/server.d.ts +12 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +187 -11
- package/dist/web/server.js.map +1 -1
- package/dist/web/sse-events.d.ts +6 -0
- package/dist/web/sse-events.d.ts.map +1 -1
- package/dist/web/sse-events.js +7 -0
- package/dist/web/sse-events.js.map +1 -1
- package/dist/web/sse-stream-manager.d.ts +32 -2
- package/dist/web/sse-stream-manager.d.ts.map +1 -1
- package/dist/web/sse-stream-manager.js +51 -4
- package/dist/web/sse-stream-manager.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/public/constants.48da7f03.js.br +0 -0
- package/dist/web/public/styles.ff43f80f.css +0 -1
- package/dist/web/public/styles.ff43f80f.css.br +0 -0
- package/dist/web/public/styles.ff43f80f.css.gz +0 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Multi-user frontend: identity boot, admin Users panel, and the
|
|
3
|
+
* change-password flow. Self-contained (builds its own DOM) so it needs no
|
|
4
|
+
* index.html surgery beyond the script tag and integrates with the existing App
|
|
5
|
+
* Settings modal by injecting a "Users" tab (admins in multi-user mode only).
|
|
6
|
+
*
|
|
7
|
+
* @dependency app.js (window.app), settings-ui.js (App Settings modal + tab switch)
|
|
8
|
+
* @loadorder after settings-ui.js / ultracode-panel.js, before session-ui.js
|
|
9
|
+
*
|
|
10
|
+
* In single-user mode GET /api/me returns a synthetic admin with multiUser:false,
|
|
11
|
+
* so none of the admin UI is shown and behavior is unchanged.
|
|
12
|
+
*/
|
|
13
|
+
(function () {
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const unwrap = (body) => (body && typeof body === 'object' && 'data' in body ? body.data : body);
|
|
17
|
+
|
|
18
|
+
async function apiGet(path) {
|
|
19
|
+
const res = await window.fetch(path, { headers: { Accept: 'application/json' } });
|
|
20
|
+
return unwrap(await res.json());
|
|
21
|
+
}
|
|
22
|
+
async function apiSend(method, path, body) {
|
|
23
|
+
const res = await window.fetch(path, {
|
|
24
|
+
method,
|
|
25
|
+
headers: body ? { 'Content-Type': 'application/json' } : {},
|
|
26
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
27
|
+
});
|
|
28
|
+
let json = null;
|
|
29
|
+
try {
|
|
30
|
+
json = await res.json();
|
|
31
|
+
} catch {
|
|
32
|
+
/* empty body */
|
|
33
|
+
}
|
|
34
|
+
return { ok: res.ok, status: res.status, body: json, data: unwrap(json) };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Change-password modal ─────────────────────────────────────────────────
|
|
38
|
+
let cpModal = null;
|
|
39
|
+
function buildChangePasswordModal() {
|
|
40
|
+
if (cpModal) return cpModal;
|
|
41
|
+
const el = document.createElement('div');
|
|
42
|
+
el.className = 'modal';
|
|
43
|
+
el.id = 'changePasswordModal';
|
|
44
|
+
el.style.zIndex = '3100';
|
|
45
|
+
el.innerHTML = `
|
|
46
|
+
<div class="modal-content" style="max-width:420px">
|
|
47
|
+
<div class="modal-header"><h2>Change Password</h2></div>
|
|
48
|
+
<div class="modal-body">
|
|
49
|
+
<p id="cpMustNote" class="form-hint" style="display:none;color:var(--warning,#c80)">
|
|
50
|
+
You must change your password before continuing.</p>
|
|
51
|
+
<div class="form-row"><label>Current password</label>
|
|
52
|
+
<input type="password" id="cpCurrent" class="form-input" autocomplete="current-password"></div>
|
|
53
|
+
<div class="form-row"><label>New password (min 8)</label>
|
|
54
|
+
<input type="password" id="cpNew" class="form-input" autocomplete="new-password"></div>
|
|
55
|
+
<div class="form-row"><label>Confirm new password</label>
|
|
56
|
+
<input type="password" id="cpConfirm" class="form-input" autocomplete="new-password"></div>
|
|
57
|
+
<p id="cpError" style="color:var(--error,#c33);min-height:1.2em"></p>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="modal-footer">
|
|
60
|
+
<button class="btn" id="cpCancel">Cancel</button>
|
|
61
|
+
<button class="btn btn-primary" id="cpSubmit">Change password</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>`;
|
|
64
|
+
document.body.appendChild(el);
|
|
65
|
+
el.querySelector('#cpCancel').onclick = () => (el.style.display = 'none');
|
|
66
|
+
el.querySelector('#cpSubmit').onclick = async () => {
|
|
67
|
+
const current = el.querySelector('#cpCurrent').value;
|
|
68
|
+
const nw = el.querySelector('#cpNew').value;
|
|
69
|
+
const confirm = el.querySelector('#cpConfirm').value;
|
|
70
|
+
const err = el.querySelector('#cpError');
|
|
71
|
+
err.textContent = '';
|
|
72
|
+
if (nw.length < 8) return (err.textContent = 'New password must be at least 8 characters.');
|
|
73
|
+
if (nw !== confirm) return (err.textContent = 'Passwords do not match.');
|
|
74
|
+
const r = await apiSend('POST', '/api/me/password', { currentPassword: current, newPassword: nw });
|
|
75
|
+
if (!r.ok) return (err.textContent = (r.body && r.body.error) || 'Change failed.');
|
|
76
|
+
el.style.display = 'none';
|
|
77
|
+
if (window.app && window.app.showToast) window.app.showToast('Password changed');
|
|
78
|
+
};
|
|
79
|
+
cpModal = el;
|
|
80
|
+
return el;
|
|
81
|
+
}
|
|
82
|
+
function openChangePassword(forced) {
|
|
83
|
+
const el = buildChangePasswordModal();
|
|
84
|
+
el.querySelector('#cpMustNote').style.display = forced ? '' : 'none';
|
|
85
|
+
el.querySelector('#cpCancel').style.display = forced ? 'none' : '';
|
|
86
|
+
el.querySelector('#cpError').textContent = '';
|
|
87
|
+
el.style.display = 'flex';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Fetch interceptor: surface PASSWORD_CHANGE_REQUIRED ───────────────────
|
|
91
|
+
function installInterceptor() {
|
|
92
|
+
const orig = window.fetch;
|
|
93
|
+
window.fetch = async function (...args) {
|
|
94
|
+
const res = await orig.apply(this, args);
|
|
95
|
+
if (res.status === 403) {
|
|
96
|
+
try {
|
|
97
|
+
const clone = res.clone();
|
|
98
|
+
const j = await clone.json();
|
|
99
|
+
if (j && j.errorCode === 'PASSWORD_CHANGE_REQUIRED') openChangePassword(true);
|
|
100
|
+
} catch {
|
|
101
|
+
/* not JSON */
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return res;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Admin Users panel (injected into the App Settings modal) ──────────────
|
|
109
|
+
function injectUsersTab() {
|
|
110
|
+
const modal = document.getElementById('appSettingsModal');
|
|
111
|
+
if (!modal || modal.querySelector('[data-tab="settings-users"]')) return;
|
|
112
|
+
const tabs = modal.querySelector('.modal-tabs');
|
|
113
|
+
const body = modal.querySelector('.modal-body');
|
|
114
|
+
if (!tabs || !body) return;
|
|
115
|
+
const btn = document.createElement('button');
|
|
116
|
+
btn.className = 'modal-tab-btn';
|
|
117
|
+
btn.dataset.tab = 'settings-users';
|
|
118
|
+
btn.textContent = 'Users';
|
|
119
|
+
tabs.appendChild(btn);
|
|
120
|
+
const content = document.createElement('div');
|
|
121
|
+
content.className = 'modal-tab-content hidden';
|
|
122
|
+
content.id = 'settings-users';
|
|
123
|
+
content.innerHTML = `
|
|
124
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
|
125
|
+
<strong>Users</strong>
|
|
126
|
+
<button class="btn btn-sm" id="adminAddUser">+ Add user</button>
|
|
127
|
+
</div>
|
|
128
|
+
<p class="form-hint">Users share the host account; this separates workspaces, it does not sandbox
|
|
129
|
+
users from each other. Pair with Docker cases for isolation.</p>
|
|
130
|
+
<div id="adminUsersTable"></div>
|
|
131
|
+
<p id="adminUsersMsg" style="min-height:1.2em;color:var(--muted,#888)"></p>`;
|
|
132
|
+
body.appendChild(content);
|
|
133
|
+
// Render whenever the tab is shown (the shared switchSettingsTab toggles it).
|
|
134
|
+
btn.addEventListener('click', renderUsers);
|
|
135
|
+
content.querySelector('#adminAddUser').onclick = addUserFlow;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function esc(s) {
|
|
139
|
+
return String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function renderUsers() {
|
|
143
|
+
const table = document.getElementById('adminUsersTable');
|
|
144
|
+
if (!table) return;
|
|
145
|
+
table.innerHTML = 'Loading…';
|
|
146
|
+
let users;
|
|
147
|
+
try {
|
|
148
|
+
users = await apiGet('/api/admin/users');
|
|
149
|
+
} catch {
|
|
150
|
+
table.innerHTML = 'Failed to load users.';
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const rows = users
|
|
154
|
+
.map((u) => {
|
|
155
|
+
const flags = [
|
|
156
|
+
u.role === 'admin' ? 'admin' : 'user',
|
|
157
|
+
u.disabled ? 'disabled' : 'enabled',
|
|
158
|
+
u.canBypassPermissions ? 'can-bypass' : '',
|
|
159
|
+
u.mustChangePassword ? 'must-change-pw' : '',
|
|
160
|
+
]
|
|
161
|
+
.filter(Boolean)
|
|
162
|
+
.join(', ');
|
|
163
|
+
const st = u.stats || {};
|
|
164
|
+
return `<tr data-u="${esc(u.username)}">
|
|
165
|
+
<td>${esc(u.username)}</td>
|
|
166
|
+
<td style="font-size:.85em;color:var(--muted,#888)">${esc(flags)}</td>
|
|
167
|
+
<td style="font-size:.85em">${st.liveSessions ?? 0} live · ${st.caseCount ?? 0} cases</td>
|
|
168
|
+
<td style="white-space:nowrap">
|
|
169
|
+
<button class="btn btn-xs" data-act="role">${u.role === 'admin' ? 'Demote' : 'Promote'}</button>
|
|
170
|
+
<button class="btn btn-xs" data-act="disabled">${u.disabled ? 'Enable' : 'Disable'}</button>
|
|
171
|
+
<button class="btn btn-xs" data-act="bypass">${u.canBypassPermissions ? 'Revoke bypass' : 'Grant bypass'}</button>
|
|
172
|
+
<button class="btn btn-xs" data-act="reset">Reset pw</button>
|
|
173
|
+
<button class="btn btn-xs" data-act="delete">Delete</button>
|
|
174
|
+
</td></tr>`;
|
|
175
|
+
})
|
|
176
|
+
.join('');
|
|
177
|
+
table.innerHTML = `<table style="width:100%;border-collapse:collapse" class="admin-users">
|
|
178
|
+
<thead><tr><th align="left">User</th><th align="left">Flags</th><th align="left">Usage</th><th></th></tr></thead>
|
|
179
|
+
<tbody>${rows}</tbody></table>`;
|
|
180
|
+
table.querySelectorAll('button[data-act]').forEach((b) => {
|
|
181
|
+
b.onclick = () =>
|
|
182
|
+
userAction(
|
|
183
|
+
b.closest('tr').dataset.u,
|
|
184
|
+
b.dataset.act,
|
|
185
|
+
users.find((x) => x.username === b.closest('tr').dataset.u)
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function setMsg(t) {
|
|
191
|
+
const m = document.getElementById('adminUsersMsg');
|
|
192
|
+
if (m) m.textContent = t || '';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function userAction(username, act, u) {
|
|
196
|
+
if (act === 'role') {
|
|
197
|
+
const r = await apiSend('PATCH', `/api/admin/users/${encodeURIComponent(username)}`, {
|
|
198
|
+
role: u.role === 'admin' ? 'user' : 'admin',
|
|
199
|
+
});
|
|
200
|
+
setMsg(r.ok ? `Updated ${username}.` : (r.body && r.body.error) || 'Failed.');
|
|
201
|
+
} else if (act === 'disabled') {
|
|
202
|
+
const r = await apiSend('PATCH', `/api/admin/users/${encodeURIComponent(username)}`, { disabled: !u.disabled });
|
|
203
|
+
setMsg(r.ok ? `Updated ${username}.` : (r.body && r.body.error) || 'Failed.');
|
|
204
|
+
} else if (act === 'bypass') {
|
|
205
|
+
const r = await apiSend('PATCH', `/api/admin/users/${encodeURIComponent(username)}`, {
|
|
206
|
+
canBypassPermissions: !u.canBypassPermissions,
|
|
207
|
+
});
|
|
208
|
+
setMsg(r.ok ? `Updated ${username}.` : (r.body && r.body.error) || 'Failed.');
|
|
209
|
+
} else if (act === 'reset') {
|
|
210
|
+
if (!window.confirm(`Reset ${username}'s password? They must set a new one on next login.`)) return;
|
|
211
|
+
const r = await apiSend('POST', `/api/admin/users/${encodeURIComponent(username)}/reset-password`);
|
|
212
|
+
if (r.ok && r.data && r.data.oneTimePassword) {
|
|
213
|
+
window.prompt(`One-time password for ${username} (copy it now — shown once):`, r.data.oneTimePassword);
|
|
214
|
+
} else setMsg((r.body && r.body.error) || 'Reset failed.');
|
|
215
|
+
} else if (act === 'delete') {
|
|
216
|
+
const typed = window.prompt(`Type "${username}" to delete this user. Add " +space" to also delete their files.`);
|
|
217
|
+
if (typed !== username && typed !== `${username} +space`) return setMsg('Delete cancelled.');
|
|
218
|
+
const deleteSpace = typed.endsWith(' +space');
|
|
219
|
+
const r = await apiSend('DELETE', `/api/admin/users/${encodeURIComponent(username)}`, { deleteSpace });
|
|
220
|
+
setMsg(r.ok ? `Deleted ${username}.` : (r.body && r.body.error) || 'Delete failed.');
|
|
221
|
+
}
|
|
222
|
+
renderUsers();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function addUserFlow() {
|
|
226
|
+
const username = window.prompt('New username (lowercase, 2-32 chars, [a-z0-9_-]):');
|
|
227
|
+
if (!username) return;
|
|
228
|
+
const admin = window.confirm('Make this user an admin? (OK = admin, Cancel = regular user)');
|
|
229
|
+
const r = await apiSend('POST', '/api/admin/users', { username: username.trim(), role: admin ? 'admin' : 'user' });
|
|
230
|
+
if (r.ok && r.data && r.data.oneTimePassword) {
|
|
231
|
+
window.prompt(`Created ${username}. One-time password (copy it now — shown once):`, r.data.oneTimePassword);
|
|
232
|
+
} else setMsg((r.body && r.body.error) || 'Create failed.');
|
|
233
|
+
renderUsers();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── Boot ──────────────────────────────────────────────────────────────────
|
|
237
|
+
async function boot() {
|
|
238
|
+
installInterceptor();
|
|
239
|
+
let me = null;
|
|
240
|
+
try {
|
|
241
|
+
me = await apiGet('/api/me');
|
|
242
|
+
} catch {
|
|
243
|
+
/* server may be pre-auth */
|
|
244
|
+
}
|
|
245
|
+
window.__codemanUser = me || { username: 'admin', role: 'admin', multiUser: false };
|
|
246
|
+
document.dispatchEvent(new CustomEvent('codeman:me', { detail: window.__codemanUser }));
|
|
247
|
+
if (window.__codemanUser.mustChangePassword) openChangePassword(true);
|
|
248
|
+
if (window.__codemanUser.multiUser && window.__codemanUser.role === 'admin') {
|
|
249
|
+
injectUsersTab();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (document.readyState === 'loading') {
|
|
254
|
+
document.addEventListener('DOMContentLoaded', boot);
|
|
255
|
+
} else {
|
|
256
|
+
boot();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
window.codemanAdmin = { openChangePassword, renderUsers };
|
|
260
|
+
})();
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -481,6 +481,9 @@ const SSE_EVENTS = {
|
|
|
481
481
|
DOCKER_IMAGE_BUILD_PROGRESS: 'docker:imageBuildProgress',
|
|
482
482
|
DOCKER_IMAGE_BUILD_COMPLETE: 'docker:imageBuildComplete',
|
|
483
483
|
DOCKER_IMAGE_BUILD_FAILED: 'docker:imageBuildFailed',
|
|
484
|
+
// Multi-user (admin-only / targeted)
|
|
485
|
+
ADMIN_USERS_CHANGED: 'admin:usersChanged',
|
|
486
|
+
AUTH_PASSWORD_CHANGE_REQUIRED: 'auth:passwordChangeRequired',
|
|
484
487
|
};
|
|
485
488
|
|
|
486
489
|
// ═══════════════════════════════════════════════════════════════
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<link rel="manifest" href="manifest.json">
|
|
17
17
|
<title>Codeman</title>
|
|
18
18
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
|
|
19
|
-
<link rel="stylesheet" href="styles.
|
|
19
|
+
<link rel="stylesheet" href="styles.9e6cdb9d.css">
|
|
20
20
|
<link rel="stylesheet" href="mobile.0644e3ac.css" media="(max-width: 1023px)">
|
|
21
21
|
<!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
|
|
22
22
|
<link rel="preload" href="vendor/xterm.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<!-- Preload critical resources — lets browser discover these during HTML parse
|
|
25
25
|
instead of waiting until <script> tags at bottom-of-body are reached. -->
|
|
26
26
|
<link rel="preload" href="vendor/xterm.min.js" as="script">
|
|
27
|
-
<link rel="preload" href="constants.
|
|
27
|
+
<link rel="preload" href="constants.5ce97313.js" as="script">
|
|
28
28
|
<link rel="preload" href="app.6c4b9893.js" as="script">
|
|
29
29
|
<!-- Self-hosted xterm.js — eliminates CDN DNS/TLS latency (~100ms).
|
|
30
30
|
'defer' preserves execution order (xterm loads before fit addon). -->
|
|
@@ -1449,6 +1449,7 @@
|
|
|
1449
1449
|
<label>Startup Mode</label>
|
|
1450
1450
|
<select id="appSettingsClaudeMode" class="form-select">
|
|
1451
1451
|
<option value="dangerously-skip-permissions">Skip Permissions (default)</option>
|
|
1452
|
+
<option value="auto">Auto (classifier-guarded, low prompts)</option>
|
|
1452
1453
|
<option value="normal">Normal (with prompts)</option>
|
|
1453
1454
|
<option value="allowedTools">Allowed Tools Only</option>
|
|
1454
1455
|
</select>
|
|
@@ -2477,7 +2478,7 @@
|
|
|
2477
2478
|
<!-- Lines drawn dynamically -->
|
|
2478
2479
|
</svg>
|
|
2479
2480
|
|
|
2480
|
-
<script defer src="constants.
|
|
2481
|
+
<script defer src="constants.5ce97313.js"></script>
|
|
2481
2482
|
<script defer src="mobile-handlers.b5a667ef.js"></script>
|
|
2482
2483
|
<script defer src="voice-input.085e9e73.js"></script>
|
|
2483
2484
|
<script defer src="notification-manager.ca9dc40d.js"></script>
|
|
@@ -2494,6 +2495,7 @@
|
|
|
2494
2495
|
<script defer src="settings-ui.29214cf1.js"></script>
|
|
2495
2496
|
<script defer src="panels-ui.3c9b0465.js"></script>
|
|
2496
2497
|
<script defer src="ultracode-panel.js"></script>
|
|
2498
|
+
<script defer src="admin-ui.js"></script>
|
|
2497
2499
|
<script defer src="session-ui.4851c61a.js"></script>
|
|
2498
2500
|
<script defer src="ralph-wizard.13a1831e.js"></script>
|
|
2499
2501
|
<script defer src="api-client.c9b1cddc.js"></script>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|