Changeset 0.10.5 (#61)

This commit is contained in:
2026-02-25 00:18:03 +00:00
parent e5ee78c498
commit d2ec55b16d
16 changed files with 1643 additions and 801 deletions

View File

@@ -61,7 +61,7 @@ async function toggleUserRole(id, role) {
} catch (e) { UI.toast(e.message, 'error'); }
}
async function deleteUser(id, username) {
if (!confirm(`Delete user "${username}"? This cannot be undone.`)) return;
if (!await showConfirm(`Delete user "${username}"? This cannot be undone.`)) return;
try { await API.adminDeleteUser(id); UI.toast('User deleted', 'success'); await UI.loadAdminUsers(); }
catch (e) { UI.toast(e.message, 'error'); }
}
@@ -89,68 +89,14 @@ async function adminResetUserPassword(id, username) {
);
if (!pw) return;
if (pw.length < 8) { UI.toast('Password must be at least 8 characters', 'warning'); return; }
if (!confirm(`Final confirmation: Reset password AND destroy personal vault for "${username}"?`)) 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'); }
}
async function deleteGlobalProvider(id) {
if (!confirm('Delete this global provider?')) return;
try { await API.adminDeleteGlobalConfig(id); UI.toast('Provider deleted', 'success'); await UI.loadAdminProviders(); }
catch (e) { UI.toast(e.message, 'error'); }
}
var _editingProviderId = null;
function editGlobalProvider(id) {
const c = UI._providerCache?.[id];
if (!c) return;
_editingProviderId = id;
const form = document.getElementById('adminAddProviderForm');
form.style.display = '';
document.getElementById('adminProvName').value = c.name || '';
document.getElementById('adminProvType').value = c.provider || 'openai';
document.getElementById('adminProvEndpoint').value = c.endpoint || '';
document.getElementById('adminProvKey').value = '';
document.getElementById('adminProvKey').placeholder = c.has_key ? '(unchanged)' : 'sk-...';
document.getElementById('adminProvModel').value = c.model_default || '';
document.getElementById('adminProvPrivate').checked = !!c.is_private;
document.getElementById('adminCreateProvBtn').textContent = 'Update';
}
async function createGlobalProvider() {
try {
const name = document.getElementById('adminProvName').value.trim();
const prov = document.getElementById('adminProvType').value;
const ep = document.getElementById('adminProvEndpoint').value.trim();
const key = document.getElementById('adminProvKey').value;
const model = document.getElementById('adminProvModel').value.trim();
const isPrivate = document.getElementById('adminProvPrivate').checked;
if (_editingProviderId) {
// Update mode
const updates = {};
if (name) updates.name = name;
if (ep) updates.endpoint = ep;
if (key) updates.api_key = key;
updates.model_default = model;
updates.is_private = isPrivate;
await API.adminUpdateGlobalConfig(_editingProviderId, updates);
UI.toast('Provider updated', 'success');
} else {
// Create mode
if (!name || !ep || !key) { UI.toast('Name, endpoint, and API key required', 'warning'); return; }
await API.adminCreateGlobalConfig(name, prov, ep, key, model, isPrivate);
UI.toast('Provider added', 'success');
}
_editingProviderId = null;
document.getElementById('adminAddProviderForm').style.display = 'none';
document.getElementById('adminCreateProvBtn').textContent = 'Save';
document.getElementById('adminProvKey').placeholder = 'sk-...';
await UI.loadAdminProviders();
} catch (e) { UI.toast(e.message, 'error'); }
}
// ── Admin Provider / Role handlers — now managed by UI primitives in ui-admin.js ──
async function fetchAdminModels() {
const hint = document.getElementById('adminModelsHint');
@@ -194,64 +140,7 @@ async function bulkSetVisibility(visibility) {
} catch (e) { UI.toast(e.message, 'error'); hint.textContent = 'Failed'; }
}
// ── Admin Roles ────────────────────────────
async function adminRoleProviderChanged(role, slot) {
const provId = document.getElementById(`role-${role}-${slot}-provider`)?.value;
const modelSelect = document.getElementById(`role-${role}-${slot}-model`);
if (!modelSelect) return;
modelSelect.innerHTML = '<option value="">— select model —</option>';
if (!provId || !window._adminModelList) return;
// Filter by provider AND model type matching the role
const typeForRole = role === 'embedding' ? 'embedding' : 'chat';
const models = window._adminModelList.filter(m =>
m.provider_config_id === provId &&
(m.model_type || 'chat') === typeForRole
);
models.forEach(m => {
const opt = document.createElement('option');
opt.value = m.model_id;
opt.textContent = m.display_name || m.model_id;
modelSelect.appendChild(opt);
});
}
async function adminSaveRole(role) {
const status = document.getElementById(`role-${role}-status`);
try {
const getBinding = (slot) => {
const prov = document.getElementById(`role-${role}-${slot}-provider`)?.value;
const model = document.getElementById(`role-${role}-${slot}-model`)?.value;
return (prov && model) ? { provider_config_id: prov, model_id: model } : null;
};
await API.adminUpdateRole(role, {
primary: getBinding('primary'),
fallback: getBinding('fallback')
});
if (status) { status.textContent = '✓ Saved'; status.style.color = 'var(--success)'; }
// Reload to reflect saved state in dropdowns
setTimeout(() => UI.loadAdminRoles(), 500);
} catch (e) {
if (status) { status.textContent = '✗ ' + e.message; status.style.color = 'var(--error)'; }
}
}
async function adminTestRole(role) {
const status = document.getElementById(`role-${role}-status`);
if (status) { status.textContent = 'Testing...'; status.style.color = 'var(--text-muted)'; }
try {
const result = await API.adminTestRole(role);
if (status) {
const fb = result.used_fallback ? ' (fallback)' : '';
status.textContent = `${result.model}${fb}`;
status.style.color = 'var(--success)';
}
} catch (e) {
if (status) { status.textContent = '✗ ' + e.message; status.style.color = 'var(--error)'; }
}
}
// ── Admin Roles — now managed by renderRoleConfig primitive in ui-admin.js ──
// ── User Model Preferences ──────────────────
@@ -284,7 +173,7 @@ async function bulkSetUserModelVisibility(show) {
}
async function deleteUserPreset(id, name) {
if (!confirm(`Delete preset "${name}"?`)) return;
if (!await showConfirm(`Delete preset "${name}"?`)) return;
try {
await API.deleteUserPreset(id);
UI.toast('Preset deleted');
@@ -432,7 +321,7 @@ async function toggleAdminPreset(id, active) {
}
async function deleteAdminPreset(id, name) {
if (!confirm(`Delete preset "${name}"?`)) return;
if (!await showConfirm(`Delete preset "${name}"?`)) return;
try {
await API.adminDeletePreset(id);
UI.toast('Preset deleted', 'success');
@@ -452,7 +341,7 @@ async function toggleTeamActive(id, active) {
}
async function deleteTeam(id, name) {
if (!confirm(`Delete team "${name}"? All members will be removed.`)) return;
if (!await showConfirm(`Delete team "${name}"? All members will be removed.`)) return;
try {
await API.adminDeleteTeam(id);
UI.toast('Team deleted');
@@ -471,7 +360,7 @@ async function updateTeamMember(teamId, memberId, role) {
}
async function removeTeamMember(teamId, memberId, email) {
if (!confirm(`Remove ${email} from team?`)) return;
if (!await showConfirm(`Remove ${email} from team?`)) return;
try {
await API.adminRemoveMember(teamId, memberId);
UI.toast('Member removed');
@@ -491,7 +380,7 @@ async function settingsUpdateTeamMember(teamId, memberId, role) {
}
async function settingsRemoveTeamMember(teamId, memberId, email) {
if (!confirm(`Remove ${email} from team?`)) return;
if (!await showConfirm(`Remove ${email} from team?`)) return;
try {
await API.teamRemoveMember(teamId, memberId);
UI.toast('Member removed');
@@ -500,7 +389,7 @@ async function settingsRemoveTeamMember(teamId, memberId, email) {
}
async function settingsDeleteTeamPreset(teamId, presetId, name) {
if (!confirm(`Delete team preset "${name}"?`)) return;
if (!await showConfirm(`Delete team preset "${name}"?`)) return;
try {
await API.teamDeletePreset(teamId, presetId);
UI.toast('Preset deleted');
@@ -509,36 +398,7 @@ async function settingsDeleteTeamPreset(teamId, presetId, name) {
} catch (e) { UI.toast(e.message, 'error'); }
}
async function settingsToggleTeamProvider(teamId, providerId, currentlyActive) {
try {
await API.teamUpdateProvider(teamId, providerId, { is_active: !currentlyActive });
UI.toast(currentlyActive ? 'Provider deactivated' : 'Provider activated');
await UI.loadTeamManageProviders(teamId);
await fetchModels();
} catch (e) { UI.toast(e.message, 'error'); }
}
async function settingsDeleteTeamProvider(teamId, providerId, name) {
if (!confirm(`Delete team provider "${name}"? This will remove all models from this provider for your team.`)) return;
try {
await API.teamDeleteProvider(teamId, providerId);
UI.toast('Provider deleted');
await UI.loadTeamManageProviders(teamId);
await fetchModels();
} catch (e) { UI.toast(e.message, 'error'); }
}
function settingsEditTeamProvider(teamId, provId, name, endpoint, defaultModel, isPrivate) {
UI._editingTeamProviderId = provId;
document.getElementById('settingsTeamAddProvider').style.display = 'none';
const form = document.getElementById('settingsTeamEditProvider');
form.style.display = '';
document.getElementById('teamProviderEditName').value = name;
document.getElementById('teamProviderEditEndpoint').value = endpoint;
document.getElementById('teamProviderEditKey').value = '';
document.getElementById('teamProviderEditDefault').value = defaultModel;
document.getElementById('teamProviderEditPrivate').checked = isPrivate;
}
// ── Team Provider handlers — now managed by UI primitives in ui-settings.js ──
// ── Admin Listeners (extracted from initListeners) ──
@@ -558,26 +418,12 @@ function _initAdminListeners() {
document.getElementById('adminCancelUserBtn')?.addEventListener('click', () => { document.getElementById('adminAddUserForm').style.display = 'none'; });
document.getElementById('adminCreateUserBtn')?.addEventListener('click', createAdminUser);
// Admin — providers
// Admin — providers (form managed by primitives in loadAdminProviders)
document.getElementById('adminAddProviderBtn')?.addEventListener('click', () => {
_editingProviderId = null;
document.getElementById('adminCreateProvBtn').textContent = 'Save';
document.getElementById('adminProvKey').placeholder = 'sk-...';
document.getElementById('adminProvName').value = '';
document.getElementById('adminProvEndpoint').value = '';
document.getElementById('adminProvKey').value = '';
document.getElementById('adminProvModel').value = '';
document.getElementById('adminProvPrivate').checked = false;
const f = document.getElementById('adminAddProviderForm');
if (UI._adminProvForm) UI._adminProvForm.setCreateMode();
f.style.display = f.style.display === 'none' ? '' : 'none';
});
document.getElementById('adminCancelProvBtn')?.addEventListener('click', () => {
document.getElementById('adminAddProviderForm').style.display = 'none';
_editingProviderId = null;
document.getElementById('adminCreateProvBtn').textContent = 'Save';
document.getElementById('adminProvKey').placeholder = 'sk-...';
});
document.getElementById('adminCreateProvBtn')?.addEventListener('click', createGlobalProvider);
// Admin — models
document.getElementById('adminFetchModelsBtn')?.addEventListener('click', fetchAdminModels);