ontoref/crates/ontoref-daemon/templates/pages/provisioning.html

178 lines
7.4 KiB
HTML
Raw Normal View History

feat: domain extension system, VCS abstraction, personal/provisioning domains, web subpages Domain extension system (ADR-012): bash-layer dispatch activates repo_kind-conditional CLI domains. install.nu copies domains/ tree; short_alias wrappers generated (personal, prov). ore help and describe capabilities domain-aware. personal domain (PersonalOntology): career skills/talks/publications/positioning, CFP pipeline (Watching→Delivered), opportunities lifecycle, content pipeline, Sessionize integration. Daemon pages: /career, /personal. provisioning domain (DevWorkspace/Mixed): FSM state, next transitions, connections graph, gates, workspace card, capabilities, backlog. Daemon page: /provisioning. VCS abstraction layer (ADR-013): reflection/modules/vcs.nu — uniform jj/git API via filesystem detection (.jj/ vs .git/). opmode.nu and git-event.nu migrated off ^git. reflection/bin/jjw.nu — jj + ontoref + Radicle agent workspace lifecycle. jjw-ncl-merge.nu registered as jj merge tool for .ontology/ NCL conflicts. init-repo.nu for new_project mode. jj/rad not in ontoref requirements — belong in orchestration project manifests. 'Framework RepoKind: ontology/schemas/manifest.ncl gains 'Framework variant; ontoref self-identifies as framework — no domain activates for the protocol itself. Web presence: personal.html and provisioning.html domain subpages. index.html gains "Project Types — Domain Extensions" section with type cards and subpage links. Nav compacted (Arch/Prov labels, solid backdrop-filter background). on+re: vcs-abstraction (adrs: adr-013) and agent-workspace-orchestration Practice nodes; 21 manifest capabilities; state.ncl catalysts updated.
2026-04-07 23:08:29 +01:00
{% extends "base.html" %}
{% import "macros/ui.html" as m %}
{% block title %}Provisioning — {{ slug }} — Ontoref{% endblock title %}
{% block nav_provisioning %}active{% endblock nav_provisioning %}
{% block nav_group_domain %}active{% endblock nav_group_domain %}
{% block content %}
<div class="mb-6 flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold">Provisioning</h1>
<p class="text-base-content/50 text-sm mt-1">Connections · Production Readiness Gates</p>
</div>
</div>
<!-- Tabs -->
<div role="tablist" class="tabs tabs-bordered mb-4">
<a role="tab" class="tab tab-active" onclick="switchTab('connections', this)">Connections</a>
<a role="tab" class="tab" onclick="switchTab('gates', this)">Gates</a>
</div>
<!-- Connections tab -->
<div id="tab-connections">
<div id="connections-content"></div>
</div>
<!-- Gates tab -->
<div id="tab-gates" class="hidden">
<div id="gates-content" class="grid grid-cols-1 lg:grid-cols-2 gap-4"></div>
</div>
<!-- Detail modal -->
<dialog id="detail-modal" class="modal">
<div class="modal-box w-full max-w-2xl">
<form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost absolute right-3 top-3"></button>
</form>
<div id="modal-content" class="text-sm space-y-3"></div>
</div>
<form method="dialog" class="modal-backdrop"><button>close</button></form>
</dialog>
<script>
const CONNECTIONS = {{ connections_json | safe }};
const GATES = {{ gates_json | safe }};
const KIND_BADGE = {
ServiceDependency: 'badge-primary',
LibraryDependency: 'badge-info',
DataDependency: 'badge-warning',
InfrastructurePeer: 'badge-ghost',
SharedOwnership: 'badge-ghost',
};
const TENSION_BADGE = {
Low: 'badge-success', Medium: 'badge-warning', High: 'badge-error',
};
function kindBadge(k) {
return `<span class="badge badge-xs font-mono ${KIND_BADGE[k] ?? 'badge-ghost'}">${k}</span>`;
}
function tensionBadge(t) {
return `<span class="badge badge-xs font-mono ${TENSION_BADGE[t] ?? 'badge-ghost'}">${t}</span>`;
}
// ── Connections ───────────────────────────────────────────────────────────────
function renderConnections() {
const container = document.getElementById('connections-content');
const sections = [
{ key: 'upstream', label: 'Upstream', desc: 'Projects this one depends on' },
{ key: 'downstream', label: 'Downstream', desc: 'Projects that depend on this one' },
{ key: 'peers', label: 'Peers', desc: 'Sibling projects in the ecosystem' },
];
const allEmpty = sections.every(s => !(CONNECTIONS[s.key]||[]).length);
if (allEmpty) {
container.innerHTML = '<p class="text-base-content/30 text-sm py-6">No connections declared</p>';
return;
}
container.innerHTML = sections.map(s => {
const items = CONNECTIONS[s.key] || [];
if (!items.length) return '';
return `
<div class="mb-6">
<div class="flex items-baseline gap-2 mb-2">
<h2 class="text-sm font-bold uppercase tracking-wider text-base-content/60">${s.label}</h2>
<span class="badge badge-xs badge-neutral">${items.length}</span>
</div>
<div class="overflow-x-auto">
<table class="table table-sm w-full bg-base-200 rounded-lg">
<thead>
<tr class="text-base-content/50 text-xs uppercase tracking-wider">
<th>Project</th><th>Kind</th><th>Via</th><th>Node</th><th>Note</th>
</tr>
</thead>
<tbody>
${items.map(c => `
<tr class="hover">
<td class="font-mono text-xs text-primary font-bold">
${c.url ? `<a href="${c.url}" target="_blank" class="link link-hover">${c.project}</a>` : c.project}
</td>
<td>${kindBadge(c.kind)}</td>
<td class="font-mono text-xs text-base-content/50">${c.via || '—'}</td>
<td class="font-mono text-xs text-base-content/60">${c.node || '—'}</td>
<td class="text-xs text-base-content/50">${c.note || ''}</td>
</tr>`).join('')}
</tbody>
</table>
</div>
</div>`;
}).join('');
}
// ── Gates ─────────────────────────────────────────────────────────────────────
function renderGates() {
const container = document.getElementById('gates-content');
container.innerHTML = GATES.length === 0
? '<p class="text-base-content/30 text-sm py-6 col-span-2">No gates declared</p>'
: GATES.map((g, i) => {
const protects = (g.protege || g.protects || []);
const conditions = (g.opening_conditions || g.conditions || []);
return `
<div class="bg-base-200 rounded-lg p-4 space-y-2 cursor-pointer hover:bg-base-300 transition-colors"
onclick="showGateDetail(${i})">
<div class="flex items-start justify-between gap-2">
<div>
<p class="font-bold text-sm">${g.name || g.id}</p>
<p class="font-mono text-xs text-base-content/40">${g.id}</p>
</div>
${g.tension ? tensionBadge(g.tension) : ''}
</div>
<p class="text-xs text-base-content/60 leading-relaxed">${g.description || ''}</p>
${protects.length ? `<p class="text-xs text-base-content/40 font-mono">protects: ${protects.join(', ')}</p>` : ''}
${conditions.length ? `<p class="text-xs text-base-content/50">Conditions: ${conditions.length}</p>` : ''}
</div>`;
}).join('');
}
function showGateDetail(i) {
const g = GATES[i];
if (!g) return;
const protects = (g.protege || g.protects || []);
const conditions = (g.opening_conditions || g.conditions || []);
document.getElementById('modal-content').innerHTML = `
<div class="flex items-baseline gap-2 flex-wrap">
<span class="font-mono font-bold text-primary">${g.id}</span>
${g.tension ? tensionBadge(g.tension) : ''}
</div>
<h2 class="text-lg font-bold">${g.name || g.id}</h2>
${g.description ? `<p class="text-base-content/70 text-sm leading-relaxed">${g.description}</p>` : ''}
${protects.length ? `<p class="text-xs text-base-content/50 font-mono">protects: ${protects.join(', ')}</p>` : ''}
${conditions.length ? `
<div>
<p class="text-xs font-semibold uppercase tracking-wider text-base-content/50 mb-1">Opening conditions</p>
<ul class="text-xs text-base-content/60 space-y-1 pl-3">
${conditions.map(c => `<li class="list-disc">${typeof c === 'string' ? c : JSON.stringify(c)}</li>`).join('')}
</ul>
</div>` : ''}
`;
document.getElementById('detail-modal').showModal();
}
// ── Tabs ──────────────────────────────────────────────────────────────────────
function switchTab(name, el) {
['connections','gates'].forEach(t => {
document.getElementById('tab-' + t).classList.toggle('hidden', t !== name);
});
document.querySelectorAll('[role="tab"]').forEach(t => t.classList.remove('tab-active'));
el.classList.add('tab-active');
}
renderConnections();
renderGates();
</script>
{% endblock content %}