Changeset 0.11.0 (#62)

This commit is contained in:
2026-02-25 13:29:15 +00:00
parent d2ec55b16d
commit c9d8e9457e
56 changed files with 5664 additions and 91 deletions

View File

@@ -501,4 +501,171 @@ function _initAdminListeners() {
await UI.loadTeamMembers(UI._teamEditId);
} catch (e) { UI.toast(e.message, 'error'); }
});
// ── Extension management ────────────────────
document.getElementById('adminInstallExtBtn')?.addEventListener('click', () => {
document.getElementById('adminInstallExtForm').style.display = '';
});
document.getElementById('adminInstallExtSubmit')?.addEventListener('click', async () => {
const extId = document.getElementById('extInstallId').value.trim();
const name = document.getElementById('extInstallName').value.trim();
if (!extId || !name) return UI.toast('ID and Name are required', 'error');
// Build manifest: merge user-provided JSON with _script
let manifest = {};
const manifestRaw = document.getElementById('extInstallManifest').value.trim();
if (manifestRaw) {
try { manifest = JSON.parse(manifestRaw); }
catch { return UI.toast('Invalid manifest JSON', 'error'); }
}
const script = document.getElementById('extInstallScript').value;
if (script) manifest._script = script;
try {
await API._post('/api/v1/admin/extensions', {
ext_id: extId,
name: name,
version: document.getElementById('extInstallVersion').value.trim() || '1.0.0',
author: document.getElementById('extInstallAuthor').value.trim(),
description: document.getElementById('extInstallDesc').value.trim(),
manifest: manifest,
is_system: document.getElementById('extInstallSystem').checked,
is_enabled: document.getElementById('extInstallEnabled').checked,
});
document.getElementById('adminInstallExtForm').style.display = 'none';
// Clear form
['extInstallId','extInstallName','extInstallVersion','extInstallAuthor','extInstallDesc','extInstallManifest','extInstallScript'].forEach(id => {
const el = document.getElementById(id);
if (el) el.value = el.type === 'checkbox' ? '' : (el.tagName === 'TEXTAREA' ? '' : '');
});
document.getElementById('extInstallVersion').value = '1.0.0';
UI.toast('Extension installed');
await UI.loadAdminExtensions();
} catch (e) { UI.toast(e.message, 'error'); }
});
}
// ── Extension admin actions (global scope for onclick) ──
async function toggleAdminExtension(id, enabled) {
try {
await API._put(`/api/v1/admin/extensions/${id}`, { is_enabled: enabled });
UI.toast(enabled ? 'Extension enabled' : 'Extension disabled');
await UI.loadAdminExtensions();
} catch (e) { UI.toast(e.message, 'error'); }
}
async function deleteAdminExtension(id, name) {
if (!await showConfirm(`Uninstall extension "${name}"? This cannot be undone.`, { danger: true })) return;
try {
await API._del(`/api/v1/admin/extensions/${id}`);
UI.toast('Extension uninstalled');
await UI.loadAdminExtensions();
} catch (e) { UI.toast(e.message, 'error'); }
}
function editAdminExtension(id) {
// Close any existing edit form
document.querySelectorAll('.ext-edit-form').forEach(el => el.remove());
const ext = (UI._adminExtensions || []).find(e => e.id === id);
if (!ext) return UI.toast('Extension not found', 'error');
// Extract manifest and _script separately
let manifest = {};
try { manifest = typeof ext.manifest === 'string' ? JSON.parse(ext.manifest) : (ext.manifest || {}); }
catch { manifest = {}; }
const script = manifest._script || '';
// Show manifest without _script for cleaner editing
const manifestClean = { ...manifest };
delete manifestClean._script;
const manifestJSON = JSON.stringify(manifestClean, null, 2);
const systemWarning = ext.is_system
? `<div style="background:var(--bg-3);border:1px solid var(--warning,#f39c12);border-radius:6px;padding:8px 12px;font-size:12px;margin-bottom:8px;color:var(--warning,#f39c12)">
⚠️ System extension — local edits will be overwritten on next deploy. Copy the code first if patching.
</div>`
: '';
const formHTML = `
<div class="ext-edit-form" data-ext-id="${id}" style="border-top:1px solid var(--border);padding:12px 0;margin-top:8px">
${systemWarning}
<div class="form-group" style="margin-bottom:8px">
<label style="font-size:12px;font-weight:600">Name</label>
<input type="text" id="extEdit-name-${id}" value="${esc(ext.name)}" style="width:100%">
</div>
<div class="form-group" style="margin-bottom:8px">
<label style="font-size:12px;font-weight:600">Description</label>
<input type="text" id="extEdit-desc-${id}" value="${esc(ext.description || '')}" style="width:100%">
</div>
<div class="form-group" style="margin-bottom:8px">
<label style="font-size:12px;font-weight:600">Manifest JSON <span class="text-muted">(without _script)</span></label>
<textarea id="extEdit-manifest-${id}" rows="8" style="width:100%;font-family:var(--mono);font-size:12px;tab-size:2">${esc(manifestJSON)}</textarea>
</div>
<div class="form-group" style="margin-bottom:8px">
<label style="font-size:12px;font-weight:600">Script <span class="text-muted">(${script.length.toLocaleString()} chars)</span></label>
<textarea id="extEdit-script-${id}" rows="16" style="width:100%;font-family:var(--mono);font-size:12px;tab-size:2;white-space:pre">${esc(script)}</textarea>
</div>
<div style="display:flex;gap:8px;justify-content:flex-end">
<button class="btn-small" onclick="this.closest('.ext-edit-form').remove()">Cancel</button>
<button class="btn-small btn-primary" onclick="saveAdminExtension('${id}')">Save Changes</button>
</div>
</div>
`;
// Find the extension row and insert the form after it
const rows = document.querySelectorAll('#adminExtensionsList .admin-user-row');
for (const row of rows) {
if (row.querySelector(`[onclick*="editAdminExtension('${id}')"]`)) {
row.insertAdjacentHTML('afterend', formHTML);
// Tab key inserts tab in script textarea
const scriptEl = document.getElementById(`extEdit-script-${id}`);
if (scriptEl) {
scriptEl.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
e.preventDefault();
const start = scriptEl.selectionStart;
const end = scriptEl.selectionEnd;
scriptEl.value = scriptEl.value.substring(0, start) + ' ' + scriptEl.value.substring(end);
scriptEl.selectionStart = scriptEl.selectionEnd = start + 4;
}
});
}
break;
}
}
}
async function saveAdminExtension(id) {
const nameEl = document.getElementById(`extEdit-name-${id}`);
const descEl = document.getElementById(`extEdit-desc-${id}`);
const manifestEl = document.getElementById(`extEdit-manifest-${id}`);
const scriptEl = document.getElementById(`extEdit-script-${id}`);
if (!nameEl || !manifestEl || !scriptEl) return;
// Parse manifest and merge _script back in
let manifest;
try {
manifest = JSON.parse(manifestEl.value.trim() || '{}');
} catch (e) {
return UI.toast('Invalid manifest JSON: ' + e.message, 'error');
}
const script = scriptEl.value;
if (script.trim()) {
manifest._script = script;
}
try {
await API._put(`/api/v1/admin/extensions/${id}`, {
name: nameEl.value.trim(),
description: descEl.value.trim(),
manifest: manifest,
});
UI.toast('Extension updated — reload page to apply changes');
await UI.loadAdminExtensions();
} catch (e) {
UI.toast(e.message, 'error');
}
}