Feat v0.5.0 realtime dialog (#30)
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-go-pg (push) Successful in 2m36s
CI/CD / test-sqlite (push) Successful in 2m43s
CI/CD / build-and-deploy (push) Successful in 1m19s

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #30.
This commit is contained in:
2026-03-30 11:07:27 +00:00
committed by xcaliber
parent eb9a2d7d27
commit 2abf406db8
21 changed files with 680 additions and 22 deletions

View File

@@ -29,6 +29,8 @@ function sourceBadge(source) {
function statusBadge(status) {
if (status === 'dormant') return html`<span class="badge" style="opacity:0.6;">dormant</span>`;
if (status === 'pending_review') return html`<span class="badge" style="background:var(--warning,#e6a817);color:#000;">review</span>`;
if (status === 'suspended') return html`<span class="badge" style="background:var(--danger,#d33);color:#fff;">suspended</span>`;
return null;
}
@@ -43,6 +45,8 @@ export default function PackagesSection() {
const [registryPkgs, setRegistryPkgs] = useState([]);
const [registryLoading, setRegistryLoading] = useState(false);
const [installing, setInstalling] = useState(false);
const [permsId, setPermsId] = useState(null); // v0.5.0: permissions drawer
const [perms, setPerms] = useState([]);
const BASE = window.__BASE__ || '';
@@ -136,6 +140,46 @@ export default function PackagesSection() {
} catch (e) { sw.toast(e.message, 'error'); }
}
// ── Permissions drawer (v0.5.0) ────────────
async function togglePerms(pkgId) {
if (permsId === pkgId) { setPermsId(null); setPerms([]); return; }
try {
const data = await sw.api.admin.packages.permissions(pkgId);
setPerms((data && data.data) || data || []);
setPermsId(pkgId);
// Close settings if open
if (expandedId === pkgId) { setExpandedId(null); setPkgSettings(null); }
} catch (e) { sw.toast(e.message, 'error'); }
}
async function grantPerm(pkgId, perm) {
try {
await sw.api.admin.packages.grantPerm(pkgId, perm);
const data = await sw.api.admin.packages.permissions(pkgId);
setPerms((data && data.data) || data || []);
load();
} catch (e) { sw.toast(e.message, 'error'); }
}
async function revokePerm(pkgId, perm) {
try {
await sw.api.admin.packages.revokePerm(pkgId, perm);
const data = await sw.api.admin.packages.permissions(pkgId);
setPerms((data && data.data) || data || []);
load();
} catch (e) { sw.toast(e.message, 'error'); }
}
async function grantAllPerms(pkgId) {
try {
await sw.api.admin.packages.grantAllPerms(pkgId);
const data = await sw.api.admin.packages.permissions(pkgId);
setPerms((data && data.data) || data || []);
sw.toast('All permissions granted', 'success');
load();
} catch (e) { sw.toast(e.message, 'error'); }
}
// ── Registry ────────────────────────────────
async function toggleRegistry() {
if (registryOpen) { setRegistryOpen(false); return; }
@@ -166,6 +210,8 @@ export default function PackagesSection() {
const dormant = packages.filter(p => p.status === 'dormant').length;
const hasManifestSettings = (pkg) =>
pkg.manifest?.settings || pkg.package_settings;
const hasDeclaredPerms = (pkg) =>
pkg.manifest?.permissions && pkg.manifest.permissions.length > 0;
return html`
<div>
@@ -211,8 +257,8 @@ export default function PackagesSection() {
<div class="admin-actions-cell">
<button class="btn-small"
onClick=${() => toggleEnabled(pkg)}
disabled=${CORE_IDS.has(pkg.id) || pkg.status === 'dormant'}
title=${pkg.status === 'dormant' ? 'Requires chat' : ''}>
disabled=${CORE_IDS.has(pkg.id) || pkg.status === 'dormant' || pkg.status === 'pending_review'}
title=${pkg.status === 'dormant' ? 'Requires chat' : pkg.status === 'pending_review' ? 'Grant permissions to activate' : ''}>
${pkg.enabled ? 'Disable' : 'Enable'}
</button>
${hasManifestSettings(pkg) && html`
@@ -220,6 +266,11 @@ export default function PackagesSection() {
${expandedId === pkg.id ? 'Close' : 'Settings'}
</button>
`}
${hasDeclaredPerms(pkg) && html`
<button class="btn-small" onClick=${() => togglePerms(pkg.id)}>
${permsId === pkg.id ? 'Close' : 'Permissions'}
</button>
`}
<button class="btn-small" onClick=${() => exportPkg(pkg.id)}>Export</button>
${pkg.source !== 'core' && !pkg.is_system && html`
<button class="btn-small btn-danger" onClick=${() => deletePkg(pkg)}>Delete</button>
@@ -247,6 +298,35 @@ export default function PackagesSection() {
}
</div>
`}
${/* ── Inline permissions drawer (v0.5.0) ── */``}
${permsId === pkg.id && html`
<div style="padding:8px 12px 12px 24px;background:var(--bg-2);border-bottom:1px solid var(--border);">
${perms.length === 0
? html`<span class="text-muted" style="font-size:12px;">No permissions declared</span>`
: html`
<div style="margin-bottom:8px;">
${perms.map(p => html`
<div key=${p.permission} style="display:flex;gap:8px;align-items:center;margin-bottom:4px;">
<code style="min-width:140px;font-size:12px;">${p.permission}</code>
<button class="btn-small ${p.granted ? 'btn-danger' : 'btn-primary'}"
style="min-width:64px;font-size:11px;"
onClick=${() => p.granted ? revokePerm(pkg.id, p.permission) : grantPerm(pkg.id, p.permission)}>
${p.granted ? 'Revoke' : 'Grant'}
</button>
${p.granted && p.granted_by && html`
<span class="text-muted" style="font-size:11px;">by ${p.granted_by}</span>
`}
</div>
`)}
</div>
${perms.some(p => !p.granted) && html`
<button class="btn-small btn-primary" onClick=${() => grantAllPerms(pkg.id)}>Grant All</button>
`}
`
}
</div>
`}
</div>
`)}
</div>