Changeset 0.30.0 (#199)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
// ==========================================
|
||||
// Renders in the "Packages" admin section via ADMIN_LOADERS.
|
||||
// Replaces admin-surfaces.js (v0.28.7).
|
||||
// v0.30.0: scope column, settings, export, registry browse.
|
||||
//
|
||||
// Exports: window._loadAdminPackages
|
||||
|
||||
@@ -20,6 +21,8 @@ async function _loadAdminPackages() {
|
||||
'<button class="btn-small" id="pkgFilterSurface">Surfaces</button>' +
|
||||
'<button class="btn-small" id="pkgFilterExtension">Extensions</button>' +
|
||||
'<button class="btn-small" id="pkgFilterFull">Full</button>' +
|
||||
'<span style="flex:1"></span>' +
|
||||
'<button class="btn-small" id="pkgBrowseRegistry">Browse Registry</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div id="adminPkgInstallForm" style="display:none;margin-bottom:16px;padding:14px;background:var(--surface);border:1px solid var(--border);border-radius:8px">' +
|
||||
@@ -30,6 +33,8 @@ async function _loadAdminPackages() {
|
||||
'<button class="btn-small" id="pkgInstallCancel">Cancel</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div id="adminPkgSettingsPanel" style="display:none;margin-bottom:16px;padding:14px;background:var(--surface);border:1px solid var(--border);border-radius:8px"></div>' +
|
||||
'<div id="adminPkgRegistryPanel" style="display:none;margin-bottom:16px;padding:14px;background:var(--surface);border:1px solid var(--border);border-radius:8px"></div>' +
|
||||
'<div id="adminPkgList" class="admin-list"><div class="loading">Loading...</div></div>';
|
||||
|
||||
var base = document.body.dataset.basePath || '';
|
||||
@@ -41,11 +46,18 @@ async function _loadAdminPackages() {
|
||||
if (btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
currentFilter = label === 'All' ? '' : label.toLowerCase();
|
||||
document.getElementById('adminPkgRegistryPanel').style.display = 'none';
|
||||
loadList();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Browse Registry button
|
||||
var browseBtn = document.getElementById('pkgBrowseRegistry');
|
||||
if (browseBtn) {
|
||||
browseBtn.addEventListener('click', loadRegistry);
|
||||
}
|
||||
|
||||
// Install form
|
||||
var installSubmit = document.getElementById('pkgInstallSubmit');
|
||||
var installCancel = document.getElementById('pkgInstallCancel');
|
||||
@@ -102,12 +114,13 @@ async function _loadAdminPackages() {
|
||||
}
|
||||
|
||||
var html = '<table class="admin-table" style="width:100%"><thead><tr>' +
|
||||
'<th style="width:30%">Package</th>' +
|
||||
'<th style="width:25%">Package</th>' +
|
||||
'<th>Type</th>' +
|
||||
'<th>Version</th>' +
|
||||
'<th>Scope</th>' +
|
||||
'<th>Source</th>' +
|
||||
'<th>Status</th>' +
|
||||
'<th style="width:100px">Actions</th>' +
|
||||
'<th style="width:140px">Actions</th>' +
|
||||
'</tr></thead><tbody>';
|
||||
|
||||
packages.forEach(function(pkg) {
|
||||
@@ -121,8 +134,15 @@ async function _loadAdminPackages() {
|
||||
core: '<span class="text-muted">core</span>',
|
||||
builtin: '<span class="text-muted">builtin</span>',
|
||||
extension: '<span class="text-muted">installed</span>',
|
||||
registry: '<span class="text-muted">registry</span>',
|
||||
}[pkg.source] || pkg.source;
|
||||
|
||||
var scopeBadge = {
|
||||
global: '<span class="text-muted">global</span>',
|
||||
team: '<span class="badge badge-info">team</span>',
|
||||
personal: '<span class="badge badge-accent">personal</span>',
|
||||
}[pkg.scope] || pkg.scope;
|
||||
|
||||
var statusBadge = pkg.enabled
|
||||
? '<span class="badge badge-success">enabled</span>'
|
||||
: '<span class="badge badge-muted">disabled</span>';
|
||||
@@ -136,6 +156,14 @@ async function _loadAdminPackages() {
|
||||
actions += '<button class="btn-small btn-ghost pkg-enable" data-id="' + pkg.id + '" title="Enable">Enable</button> ';
|
||||
}
|
||||
}
|
||||
// Settings (if manifest has settings)
|
||||
if (pkg.manifest && pkg.manifest.settings && pkg.manifest.settings.length > 0) {
|
||||
actions += '<button class="btn-small btn-ghost pkg-settings" data-id="' + pkg.id + '" title="Settings">Settings</button> ';
|
||||
}
|
||||
// Export (non-core only)
|
||||
if (pkg.source !== 'core') {
|
||||
actions += '<button class="btn-small btn-ghost pkg-export" data-id="' + pkg.id + '" title="Export">Export</button> ';
|
||||
}
|
||||
// Delete (non-core only)
|
||||
if (pkg.source !== 'core') {
|
||||
actions += '<button class="btn-small btn-danger-ghost pkg-delete" data-id="' + pkg.id + '" title="Delete">Delete</button>';
|
||||
@@ -143,11 +171,13 @@ async function _loadAdminPackages() {
|
||||
|
||||
var desc = pkg.description ? '<div class="text-muted" style="font-size:11px">' + pkg.description + '</div>' : '';
|
||||
var system = pkg.is_system ? ' <span class="badge badge-muted" style="font-size:10px">system</span>' : '';
|
||||
var schemaVer = pkg.schema_version > 0 ? ' <span class="text-muted" style="font-size:10px">v' + pkg.schema_version + '</span>' : '';
|
||||
|
||||
html += '<tr>' +
|
||||
'<td><strong>' + pkg.title + '</strong>' + system + '<div class="text-muted" style="font-size:11px">' + pkg.id + '</div>' + desc + '</td>' +
|
||||
'<td><strong>' + pkg.title + '</strong>' + system + schemaVer + '<div class="text-muted" style="font-size:11px">' + pkg.id + '</div>' + desc + '</td>' +
|
||||
'<td>' + typeBadge + '</td>' +
|
||||
'<td style="font-size:12px;font-family:monospace">' + (pkg.version || '—') + '</td>' +
|
||||
'<td style="font-size:12px;font-family:monospace">' + (pkg.version || '\u2014') + '</td>' +
|
||||
'<td>' + scopeBadge + '</td>' +
|
||||
'<td>' + sourceBadge + '</td>' +
|
||||
'<td>' + statusBadge + '</td>' +
|
||||
'<td>' + actions + '</td>' +
|
||||
@@ -180,11 +210,170 @@ async function _loadAdminPackages() {
|
||||
loadList();
|
||||
});
|
||||
});
|
||||
listEl.querySelectorAll('.pkg-export').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
window.location = base + '/api/v1/admin/packages/' + btn.dataset.id + '/export';
|
||||
});
|
||||
});
|
||||
listEl.querySelectorAll('.pkg-settings').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
loadSettings(btn.dataset.id);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
listEl.innerHTML = '<div style="padding:12px;color:var(--danger);">Failed to load packages: ' + e.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Settings panel ──────────────────────────
|
||||
|
||||
async function loadSettings(pkgId) {
|
||||
var panel = document.getElementById('adminPkgSettingsPanel');
|
||||
panel.style.display = 'block';
|
||||
panel.innerHTML = '<div class="loading">Loading settings...</div>';
|
||||
|
||||
try {
|
||||
var resp = await API._get('/api/v1/admin/packages/' + pkgId + '/settings');
|
||||
var schema = resp.schema || [];
|
||||
var values = resp.values || {};
|
||||
|
||||
if (typeof values === 'string') {
|
||||
try { values = JSON.parse(values); } catch(e) { values = {}; }
|
||||
}
|
||||
|
||||
if (schema.length === 0) {
|
||||
panel.innerHTML = '<p class="text-muted" style="font-size:12px">No settings declared by this package.</p>' +
|
||||
'<button class="btn-small" id="pkgSettingsClose">Close</button>';
|
||||
document.getElementById('pkgSettingsClose').addEventListener('click', function() { panel.style.display = 'none'; });
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '<h4 style="margin:0 0 12px 0;font-size:13px">Settings: ' + pkgId + '</h4>';
|
||||
schema.forEach(function(s) {
|
||||
var key = s.key;
|
||||
var val = values[key] !== undefined ? values[key] : (s.default !== undefined ? s.default : '');
|
||||
html += '<div style="margin-bottom:10px">' +
|
||||
'<label style="font-size:12px;display:block;margin-bottom:4px">' + (s.label || key) + '</label>';
|
||||
|
||||
if (s.type === 'select' && s.options) {
|
||||
html += '<select class="pkg-setting-input" data-key="' + key + '" style="font-size:12px;padding:4px 8px">';
|
||||
s.options.forEach(function(opt) {
|
||||
html += '<option value="' + opt + '"' + (opt === val ? ' selected' : '') + '>' + opt + '</option>';
|
||||
});
|
||||
html += '</select>';
|
||||
} else if (s.type === 'boolean') {
|
||||
html += '<input type="checkbox" class="pkg-setting-input" data-key="' + key + '" data-type="boolean"' + (val ? ' checked' : '') + '>';
|
||||
} else if (s.type === 'number') {
|
||||
html += '<input type="number" class="pkg-setting-input" data-key="' + key + '" data-type="number" value="' + val + '" style="font-size:12px;padding:4px 8px;width:100px">';
|
||||
} else {
|
||||
html += '<input type="text" class="pkg-setting-input" data-key="' + key + '" value="' + val + '" style="font-size:12px;padding:4px 8px;width:200px">';
|
||||
}
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
html += '<div style="display:flex;gap:8px;margin-top:12px">' +
|
||||
'<button class="btn-small btn-primary" id="pkgSettingsSave">Save</button>' +
|
||||
'<button class="btn-small" id="pkgSettingsClose">Close</button>' +
|
||||
'</div>';
|
||||
|
||||
panel.innerHTML = html;
|
||||
|
||||
document.getElementById('pkgSettingsSave').addEventListener('click', async function() {
|
||||
var data = {};
|
||||
panel.querySelectorAll('.pkg-setting-input').forEach(function(el) {
|
||||
var key = el.dataset.key;
|
||||
if (el.dataset.type === 'boolean') {
|
||||
data[key] = el.checked;
|
||||
} else if (el.dataset.type === 'number') {
|
||||
data[key] = parseFloat(el.value) || 0;
|
||||
} else {
|
||||
data[key] = el.value;
|
||||
}
|
||||
});
|
||||
try {
|
||||
await API._put('/api/v1/admin/packages/' + pkgId + '/settings', data);
|
||||
UI.toast('Settings saved', 'success');
|
||||
panel.style.display = 'none';
|
||||
} catch (e) {
|
||||
UI.toast('Failed to save: ' + e.message, 'error');
|
||||
}
|
||||
});
|
||||
document.getElementById('pkgSettingsClose').addEventListener('click', function() { panel.style.display = 'none'; });
|
||||
} catch (e) {
|
||||
panel.innerHTML = '<div style="color:var(--danger)">Failed to load settings: ' + e.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Registry browse ─────────────────────────
|
||||
|
||||
async function loadRegistry() {
|
||||
var panel = document.getElementById('adminPkgRegistryPanel');
|
||||
panel.style.display = 'block';
|
||||
panel.innerHTML = '<div class="loading">Loading registry...</div>';
|
||||
|
||||
try {
|
||||
var resp = await API._get('/api/v1/admin/packages/registry');
|
||||
var packages = resp.packages || [];
|
||||
var registryUrl = resp.registry_url || '';
|
||||
|
||||
var html = '<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px">' +
|
||||
'<h4 style="margin:0;font-size:13px">Package Registry</h4>' +
|
||||
'<span style="flex:1"></span>' +
|
||||
'<button class="btn-small" id="pkgRegistryClose">Close</button>' +
|
||||
'</div>';
|
||||
|
||||
if (!registryUrl) {
|
||||
html += '<p class="text-muted" style="font-size:12px">No registry URL configured. Set <code>package_registry</code> in Admin Settings with a <code>{"url": "https://..."}</code> value.</p>';
|
||||
} else if (packages.length === 0) {
|
||||
html += '<p class="text-muted" style="font-size:12px">Registry is empty or returned no packages.</p>';
|
||||
} else {
|
||||
html += '<table class="admin-table" style="width:100%"><thead><tr>' +
|
||||
'<th style="width:30%">Package</th><th>Version</th><th>Author</th><th>Actions</th>' +
|
||||
'</tr></thead><tbody>';
|
||||
|
||||
packages.forEach(function(pkg) {
|
||||
var installBtn = pkg.installed
|
||||
? '<span class="badge badge-success">Installed</span>'
|
||||
: '<button class="btn-small btn-primary registry-install" data-url="' + pkg.download_url + '">Install</button>';
|
||||
|
||||
html += '<tr>' +
|
||||
'<td><strong>' + pkg.title + '</strong><div class="text-muted" style="font-size:11px">' +
|
||||
(pkg.description || '') + '</div></td>' +
|
||||
'<td style="font-family:monospace;font-size:12px">' + (pkg.version || '') + '</td>' +
|
||||
'<td style="font-size:12px">' + (pkg.author || '') + '</td>' +
|
||||
'<td>' + installBtn + '</td>' +
|
||||
'</tr>';
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
}
|
||||
|
||||
panel.innerHTML = html;
|
||||
|
||||
document.getElementById('pkgRegistryClose').addEventListener('click', function() { panel.style.display = 'none'; });
|
||||
|
||||
panel.querySelectorAll('.registry-install').forEach(function(btn) {
|
||||
btn.addEventListener('click', async function() {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Installing...';
|
||||
try {
|
||||
await API._post('/api/v1/admin/packages/registry/install', {
|
||||
download_url: btn.dataset.url,
|
||||
});
|
||||
UI.toast('Installed from registry', 'success');
|
||||
loadList();
|
||||
loadRegistry(); // refresh to show "Installed" badge
|
||||
} catch (e) {
|
||||
UI.toast('Install failed: ' + e.message, 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Install';
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
panel.innerHTML = '<div style="color:var(--danger)">Failed to load registry: ' + e.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
loadList();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user