Feat v0.3.7 package audit (#20)
All checks were successful
All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #20.
This commit is contained in:
@@ -27,6 +27,11 @@ function sourceBadge(source) {
|
||||
return html`<span class="badge ${cls}">${source}</span>`;
|
||||
}
|
||||
|
||||
function statusBadge(status) {
|
||||
if (status === 'dormant') return html`<span class="badge" style="opacity:0.6;">dormant</span>`;
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function PackagesSection() {
|
||||
const [packages, setPackages] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -56,6 +61,10 @@ export default function PackagesSection() {
|
||||
// ── Actions ─────────────────────────────────
|
||||
async function toggleEnabled(pkg) {
|
||||
if (CORE_IDS.has(pkg.id)) return;
|
||||
if (pkg.status === 'dormant') {
|
||||
sw.toast('This package requires chat — it cannot be enabled yet', 'error');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (pkg.enabled) {
|
||||
await sw.api.admin.packages.disable(pkg.id);
|
||||
@@ -154,6 +163,7 @@ export default function PackagesSection() {
|
||||
if (loading) return html`<div class="settings-placeholder">Loading\u2026</div>`;
|
||||
|
||||
const enabled = packages.filter(p => p.enabled).length;
|
||||
const dormant = packages.filter(p => p.status === 'dormant').length;
|
||||
const hasManifestSettings = (pkg) =>
|
||||
pkg.manifest?.settings || pkg.package_settings;
|
||||
|
||||
@@ -163,6 +173,7 @@ export default function PackagesSection() {
|
||||
<div class="stat-cards-grid" style="margin-bottom:16px;">
|
||||
<div class="stat-card"><div class="stat-value">${packages.length}</div><div class="stat-label">Total</div></div>
|
||||
<div class="stat-card"><div class="stat-value">${enabled}</div><div class="stat-label">Enabled</div></div>
|
||||
${dormant > 0 && html`<div class="stat-card"><div class="stat-value">${dormant}</div><div class="stat-label">Dormant</div></div>`}
|
||||
</div>
|
||||
|
||||
${/* ── Action bar ─────────────── */``}
|
||||
@@ -194,12 +205,14 @@ export default function PackagesSection() {
|
||||
${' '}${typeBadge(pkg.type)}
|
||||
${' '}${tierBadge(pkg.tier)}
|
||||
${sourceBadge(pkg.source)}
|
||||
${statusBadge(pkg.status)}
|
||||
${pkg.is_system && html`<span class="badge" style="margin-left:4px;">system</span>`}
|
||||
</div>
|
||||
<div class="admin-actions-cell">
|
||||
<button class="btn-small"
|
||||
onClick=${() => toggleEnabled(pkg)}
|
||||
disabled=${CORE_IDS.has(pkg.id)}>
|
||||
disabled=${CORE_IDS.has(pkg.id) || pkg.status === 'dormant'}
|
||||
title=${pkg.status === 'dormant' ? 'Requires chat' : ''}>
|
||||
${pkg.enabled ? 'Disable' : 'Enable'}
|
||||
</button>
|
||||
${hasManifestSettings(pkg) && html`
|
||||
|
||||
@@ -15,8 +15,10 @@ export default function TeamsSection() {
|
||||
|
||||
const loadTeams = useCallback(async () => {
|
||||
try {
|
||||
const data = await sw.api.admin.teams.list();
|
||||
setTeams(data || []);
|
||||
const resp = await sw.api.admin.teams.list();
|
||||
// Paginated response: { data: [...], total, page }
|
||||
const list = Array.isArray(resp) ? resp : (resp && resp.data ? resp.data : []);
|
||||
setTeams(list);
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
finally { setLoading(false); }
|
||||
}, []);
|
||||
|
||||
@@ -59,16 +59,22 @@ export default function WorkflowsSection({ teamId }) {
|
||||
|
||||
// ── Tab 1: Workflows ────────────────────────
|
||||
|
||||
function _unwrapList(resp) {
|
||||
return Array.isArray(resp) ? resp : (resp && resp.data ? resp.data : []);
|
||||
}
|
||||
|
||||
function WorkflowsTab({ teamId }) {
|
||||
const [workflows, setWorkflows] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [editing, setEditing] = useState(null);
|
||||
const [showCreate, setShowCreate] = useState(false);
|
||||
const [showAdopt, setShowAdopt] = useState(false);
|
||||
const [available, setAvailable] = useState([]);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
const data = await sw.api.teams.workflows(teamId);
|
||||
setWorkflows(data || []);
|
||||
setWorkflows(_unwrapList(data));
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
finally { setLoading(false); }
|
||||
}, [teamId]);
|
||||
@@ -91,6 +97,23 @@ function WorkflowsTab({ teamId }) {
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
async function openAdopt() {
|
||||
setShowAdopt(true);
|
||||
try {
|
||||
const data = await sw.api.teams.availableWorkflows(teamId);
|
||||
setAvailable(_unwrapList(data));
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
async function adoptWorkflow(wfId) {
|
||||
try {
|
||||
await sw.api.teams.adoptWorkflow(teamId, wfId);
|
||||
sw.toast('Workflow adopted', 'success');
|
||||
setShowAdopt(false);
|
||||
load();
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
if (loading) return html`<div class="settings-placeholder">Loading\u2026</div>`;
|
||||
|
||||
if (editing) return html`<${WorkflowEditor} teamId=${teamId} workflow=${editing}
|
||||
@@ -100,9 +123,33 @@ function WorkflowsTab({ teamId }) {
|
||||
<div>
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
|
||||
<div style="flex:1"></div>
|
||||
<button class="btn-md" onClick=${openAdopt}>Adopt Global</button>
|
||||
<button class="btn-md btn-primary" onClick=${() => setShowCreate(true)}>+ Add</button>
|
||||
</div>
|
||||
|
||||
${showAdopt && html`
|
||||
<div class="admin-inline-form" style="margin-bottom:16px;">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
|
||||
<strong>Adopt a Global Workflow</strong>
|
||||
<div style="flex:1"></div>
|
||||
<button class="btn-small" onClick=${() => setShowAdopt(false)}>Cancel</button>
|
||||
</div>
|
||||
${available.length === 0 && html`<div class="empty-hint">No global workflows available</div>`}
|
||||
<div class="admin-list">
|
||||
${available.map(w => html`
|
||||
<div class="admin-surface-row" key=${w.id}>
|
||||
<div style="flex:1;min-width:0;">
|
||||
<strong>${w.name}</strong>
|
||||
<span class="text-muted" style="margin-left:8px;font-size:11px;">/${w.slug}</span>
|
||||
</div>
|
||||
<span class="badge">${w.entry_mode}</span>
|
||||
<button class="btn-small btn-primary" onClick=${() => adoptWorkflow(w.id)}>Adopt</button>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
</div>
|
||||
`}
|
||||
|
||||
${showCreate && html`
|
||||
<form class="admin-inline-form" style="margin-bottom:16px;" onSubmit=${createWorkflow}>
|
||||
<div class="form-row">
|
||||
@@ -123,7 +170,7 @@ function WorkflowsTab({ teamId }) {
|
||||
`}
|
||||
|
||||
<div class="admin-list">
|
||||
${workflows.length === 0 && html`<div class="empty-hint">No workflows</div>`}
|
||||
${workflows.length === 0 && html`<div class="empty-hint">No workflows — create one or adopt from global</div>`}
|
||||
${workflows.map(w => html`
|
||||
<div class="admin-surface-row" key=${w.id} style="cursor:pointer;" onClick=${() => setEditing(w)}>
|
||||
<div style="flex:1;min-width:0;">
|
||||
@@ -245,6 +292,24 @@ function WorkflowEditor({ teamId, workflow, onBack }) {
|
||||
<span class="toggle-track"></span><span>Active</span>
|
||||
</label>
|
||||
|
||||
${workflow.entry_mode === 'public_link' && html`
|
||||
<div style="margin:8px 0;">
|
||||
<label style="font-size:11px;color:var(--text-2);display:block;margin-bottom:4px;">Public Entry URL</label>
|
||||
<div style="display:flex;gap:6px;align-items:center;">
|
||||
<input type="text" readOnly value=${window.location.origin + (window.__BASE__ || '') + '/api/v1/public/workflows/' + workflow.id + '/start'}
|
||||
style="flex:1;font-size:11px;padding:4px 8px;background:var(--input-bg);border:1px solid var(--border);border-radius:4px;color:var(--text);font-family:var(--mono);"
|
||||
onClick=${e => e.target.select()} />
|
||||
<button type="button" class="btn-small" onClick=${(e) => {
|
||||
const url = window.location.origin + (window.__BASE__ || '') + '/api/v1/public/workflows/' + workflow.id + '/start';
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
e.target.textContent = 'Copied!';
|
||||
setTimeout(() => { e.target.textContent = 'Copy'; }, 1500);
|
||||
});
|
||||
}}>Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
`}
|
||||
|
||||
<div class="form-row" style="margin-top:16px;gap:8px;">
|
||||
<button type="submit" class="btn-small btn-primary">Save</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user