Changeset 0.22.10 (#152)

This commit is contained in:
2026-03-04 22:37:57 +00:00
parent 6ed76fb2d3
commit 40d9834f64
15 changed files with 1014 additions and 515 deletions

View File

@@ -17,38 +17,37 @@ async function toggleUserActive(id, active) {
}
async function showApproveForm(userId, username) {
const form = document.getElementById(`approveForm-${userId}`);
const teamsEl = document.getElementById(`approveTeams-${userId}`);
if (!form || !teamsEl) return;
// Load teams for checkboxes
document.getElementById('approveUserId').value = userId;
document.getElementById('approveUserTitle').textContent = `Approve "${username}"`;
const teamsEl = document.getElementById('approveTeamsList');
teamsEl.innerHTML = '<span class="text-muted">Loading teams...</span>';
form.style.display = '';
openModal('approveUserModal');
try {
const resp = await API.adminListTeams();
const teams = (resp.data || []).filter(t => t.is_active);
if (teams.length === 0) {
teamsEl.innerHTML = '<span class="text-muted">No teams — user will be activated without team assignment</span>';
} else {
teamsEl.innerHTML = '<label class="form-label" style="font-size:12px;margin-bottom:4px">Assign to teams:</label>' +
teams.map(t => `<label class="checkbox-label" style="font-size:13px"><input type="checkbox" value="${t.id}" class="approve-team-cb"> ${esc(t.name)}</label>`).join('');
teamsEl.innerHTML = '<label class="form-label" style="font-size:12px;margin-bottom:8px;display:block">Assign to teams:</label>' +
teams.map(t => `<label class="toggle-label" style="margin-bottom:6px"><input type="checkbox" value="${t.id}" class="approve-team-cb"> <span>${esc(t.name)}</span></label>`).join('');
}
} catch (e) { teamsEl.innerHTML = `<span class="text-muted">Could not load teams</span>`; }
}
function hideApproveForm(userId) {
const form = document.getElementById(`approveForm-${userId}`);
if (form) form.style.display = 'none';
closeModal('approveUserModal');
}
async function submitApproval(userId) {
const form = document.getElementById(`approveForm-${userId}`);
const teamIds = [...(form?.querySelectorAll('.approve-team-cb:checked') || [])].map(cb => cb.value);
if (!userId) userId = document.getElementById('approveUserId').value;
const teamIds = [...(document.querySelectorAll('#approveTeamsList .approve-team-cb:checked') || [])].map(cb => cb.value);
try {
const el = _adminScroll(), pos = el?.scrollTop || 0;
await API.adminToggleActive(userId, true, teamIds, 'member');
const msg = teamIds.length ? `User activated & assigned to ${teamIds.length} team(s)` : 'User activated';
UI.toast(msg, 'success');
closeModal('approveUserModal');
await UI.loadAdminUsers();
_restoreScroll(el, pos);
} catch (e) { UI.toast(e.message, 'error'); }
@@ -73,27 +72,38 @@ async function createAdminUser() {
const r = document.getElementById('adminNewRole').value;
if (!u || !e || !p) { UI.toast('All fields required', 'warning'); return; }
await API.adminCreateUser(u, e, p, r);
document.getElementById('adminAddUserForm').style.display = 'none';
closeModal('createUserModal');
// Clear form fields
document.getElementById('adminNewUsername').value = '';
document.getElementById('adminNewEmail').value = '';
document.getElementById('adminNewPassword').value = '';
document.getElementById('adminNewRole').value = 'user';
UI.toast('User created', 'success');
await UI.loadAdminUsers();
} catch (e) { UI.toast(e.message, 'error'); }
}
async function adminResetUserPassword(id, username) {
const pw = prompt(
`Reset password for "${username}"?\n\n` +
`⚠️ WARNING: This will DESTROY the user's personal vault.\n` +
`All personal API keys (BYOK) will be permanently deleted.\n` +
`The user will need to re-add any personal provider keys.\n\n` +
`Enter new password (min 8 chars):`
);
if (!pw) return;
if (pw.length < 8) { UI.toast('Password must be at least 8 characters', 'warning'); return; }
if (!await showConfirm(`Final confirmation: Reset password AND destroy personal vault for "${username}"?`)) return;
try {
await API.adminResetPassword(id, pw);
UI.toast(`Password reset for ${username}. Vault destroyed.`, 'success');
} catch (e) { UI.toast(e.message, 'error'); }
// Show the reset password modal
document.getElementById('resetPwUser').textContent = username;
document.getElementById('resetPwInput').value = '';
openModal('resetPwModal');
document.getElementById('resetPwInput').focus();
// Wire up the confirm button (remove old listener, add new)
const btn = document.getElementById('resetPwConfirmBtn');
const newBtn = btn.cloneNode(true);
btn.parentNode.replaceChild(newBtn, btn);
newBtn.addEventListener('click', async () => {
const pw = document.getElementById('resetPwInput').value;
if (!pw || pw.length < 8) { UI.toast('Password must be at least 8 characters', 'warning'); return; }
if (!await showConfirm(`Final confirmation: Reset password AND destroy personal vault for "${username}"?`)) return;
try {
await API.adminResetPassword(id, pw);
closeModal('resetPwModal');
UI.toast(`Password reset for ${username}. Vault destroyed.`, 'success');
} catch (e) { UI.toast(e.message, 'error'); }
});
}
// ── Admin Provider / Role handlers — now managed by UI primitives in ui-admin.js ──
@@ -197,7 +207,7 @@ function ensureAdminPersonaForm() {
kbScope: 'admin',
onSubmit: (vals) => createAdminPersona(vals),
onCancel: () => {
container.style.display = 'none';
closeModal('personaFormModal');
_editingPersonaId = null;
_adminPersonaForm.setSubmitLabel('Create');
_adminPersonaForm.clearForm();
@@ -262,7 +272,8 @@ function editAdminPersona(id) {
ensureAdminPersonaForm();
const form = _adminPersonaForm;
if (!form) return;
document.getElementById('adminAddPersonaForm').style.display = '';
document.getElementById('personaFormTitle').textContent = 'Edit Persona';
openModal('personaFormModal');
form.setValues(p);
form.setSubmitLabel('Update');
// Load KB bindings (v0.17.0)
@@ -312,7 +323,7 @@ async function createAdminPersona(vals) {
}
_editingPersonaId = null;
document.getElementById('adminAddPersonaForm').style.display = 'none';
closeModal('personaFormModal');
if (_adminPersonaForm) {
_adminPersonaForm.setSubmitLabel('Create');
_adminPersonaForm.clearForm();
@@ -467,17 +478,18 @@ function _initAdminListeners() {
// Admin — users
document.getElementById('adminAddUserBtn')?.addEventListener('click', () => {
const f = document.getElementById('adminAddUserForm');
f.style.display = f.style.display === 'none' ? '' : 'none';
openModal('createUserModal');
});
document.getElementById('adminCancelUserBtn')?.addEventListener('click', () => { document.getElementById('adminAddUserForm').style.display = 'none'; });
document.getElementById('adminCreateUserBtn')?.addEventListener('click', createAdminUser);
// Admin — approve user modal
document.getElementById('approveUserSubmitBtn')?.addEventListener('click', () => submitApproval());
// Admin — providers (form managed by primitives in loadAdminProviders)
document.getElementById('adminAddProviderBtn')?.addEventListener('click', () => {
const f = document.getElementById('adminAddProviderForm');
if (UI._adminProvForm) UI._adminProvForm.setCreateMode();
f.style.display = f.style.display === 'none' ? '' : 'none';
document.getElementById('providerFormTitle').textContent = 'Add Provider';
openModal('providerFormModal');
});
// Admin — models
@@ -490,19 +502,17 @@ function _initAdminListeners() {
_editingPersonaId = null;
form.setSubmitLabel('Create');
form.clearForm();
const f = document.getElementById('adminAddPersonaForm');
f.style.display = f.style.display === 'none' ? '' : 'none';
document.getElementById('personaFormTitle').textContent = 'Create Persona';
openModal('personaFormModal');
});
// Admin — Teams
document.getElementById('adminAddTeamBtn')?.addEventListener('click', () => {
document.getElementById('adminAddTeamForm').style.display = '';
openModal('createTeamModal');
document.getElementById('adminTeamName').focus();
});
document.getElementById('adminCancelTeamBtn')?.addEventListener('click', () => {
document.getElementById('adminAddTeamForm').style.display = 'none';
document.getElementById('adminTeamName').value = '';
document.getElementById('adminTeamDesc').value = '';
closeModal('createTeamModal');
});
document.getElementById('adminCreateTeamBtn')?.addEventListener('click', async () => {
const name = document.getElementById('adminTeamName').value.trim();
@@ -510,7 +520,7 @@ function _initAdminListeners() {
if (!name) return UI.toast('Team name required', 'error');
try {
await API.adminCreateTeam(name, desc);
document.getElementById('adminAddTeamForm').style.display = 'none';
closeModal('createTeamModal');
document.getElementById('adminTeamName').value = '';
document.getElementById('adminTeamDesc').value = '';
UI.toast('Team created');
@@ -559,7 +569,7 @@ function _initAdminListeners() {
// ── Group management ────────────────────────
document.getElementById('adminAddGroupBtn')?.addEventListener('click', async () => {
document.getElementById('adminAddGroupForm').style.display = '';
openModal('createGroupModal');
document.getElementById('adminGroupName').focus();
// Populate team dropdown
try {
@@ -574,9 +584,7 @@ function _initAdminListeners() {
document.getElementById('adminGroupTeamRow').style.display = this.value === 'team' ? '' : 'none';
});
document.getElementById('adminCancelGroupBtn')?.addEventListener('click', () => {
document.getElementById('adminAddGroupForm').style.display = 'none';
document.getElementById('adminGroupName').value = '';
document.getElementById('adminGroupDesc').value = '';
closeModal('createGroupModal');
});
document.getElementById('adminCreateGroupBtn')?.addEventListener('click', async () => {
const name = document.getElementById('adminGroupName').value.trim();
@@ -587,7 +595,7 @@ function _initAdminListeners() {
if (scope === 'team' && !teamId) return UI.toast('Select a team for team-scoped groups', 'error');
try {
await API.adminCreateGroup(name, desc, scope, teamId);
document.getElementById('adminAddGroupForm').style.display = 'none';
closeModal('createGroupModal');
document.getElementById('adminGroupName').value = '';
document.getElementById('adminGroupDesc').value = '';
UI.toast('Group created');
@@ -783,7 +791,7 @@ function editAdminExtension(id) {
`;
// Find the extension row and insert the form after it
const rows = document.querySelectorAll('#adminExtensionsList .admin-user-row');
const rows = document.querySelectorAll('#adminExtensionsList tbody tr');
for (const row of rows) {
if (row.querySelector(`[onclick*="editAdminExtension('${id}')"]`)) {
row.insertAdjacentHTML('afterend', formHTML);