Changeset 0.11.0 (#62)
This commit is contained in:
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user