provisioning-platform/crates/provisioning-daemon/ui/templates/pages/workspace_ontology.html

102 lines
6.1 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% import "macros/ui.html" as m %}
{% block title %}{{ ws_name }} — Ontology{% endblock %}
{% block nav_workspaces %}btn-active{% endblock %}
{% block main_class %}p-0{% endblock %}
{% block head %}
<style>
#graph-root { display:flex; height:calc(100vh - 64px - 88px); min-height:500px; }
#cy-wrapper { flex:1 1 auto; min-width:0; position:relative; }
#cy { width:100%; height:100%; }
#cy-controls { position:absolute; bottom:14px; right:14px; z-index:10; display:flex; flex-direction:column; gap:4px; }
#cy-controls button { width:30px; height:30px; border-radius:6px; border:1px solid rgba(255,255,255,0.1); background:rgba(0,0,0,0.6); color:#fff; font-size:16px; cursor:pointer; }
#cy-controls button:hover { background:rgba(80,80,80,0.8); }
#sidebar { width:280px; flex-shrink:0; overflow-y:auto; background:oklch(var(--b2,20% 0 0)); border-left:1px solid oklch(var(--b3,15% 0 0)); padding:1rem; font-size:0.75rem; }
</style>
{% endblock %}
{% block content %}
<div class="px-4 pt-3 pb-1">
{{ m::ws_tabs(ws_name=ws_name, active=ws_active_tab, ws_envs=ws_envs, ws_has_clusters=ws_has_clusters, ws_has_workflows=ws_has_workflows, current_env=current_env) }}
</div>
<div class="px-4 pb-2 flex items-center gap-4">
<h2 class="text-base font-bold font-mono">
Ontology
<span class="text-base-content/30 font-normal text-sm ml-2">{{ ws_name }}</span>
</h2>
<div class="flex gap-2 text-xs">
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" id="show-axioms" checked class="checkbox checkbox-xs">
<span class="badge badge-primary badge-xs">Axioms</span>
</label>
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" id="show-practices" checked class="checkbox checkbox-xs">
<span class="badge badge-success badge-xs">Practices</span>
</label>
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" id="show-tensions" checked class="checkbox checkbox-xs">
<span class="badge badge-warning badge-xs">Tensions</span>
</label>
</div>
{% if not has_root %}
<div class="alert alert-warning alert-xs py-1 px-3 text-xs">Workspace path not resolved.</div>
{% endif %}
</div>
<div id="graph-root">
<div id="cy-wrapper">
<div id="cy"></div>
<div id="cy-controls">
<button onclick="cy.fit()" title="Fit"></button>
<button onclick="cy.zoom(cy.zoom()*1.25)" title="Zoom in">+</button>
<button onclick="cy.zoom(cy.zoom()*0.8)" title="Zoom out"></button>
<button onclick="resetLayout()" title="Re-layout"></button>
</div>
</div>
<div id="sidebar">
<p class="text-base-content/40 text-xs mb-3">Click a node to inspect.</p>
<div id="node-detail"></div>
</div>
</div>
<script>
const GRAPH = {{ graph_json | safe }};
let cy = null;
function levelColor(l){return l==='Axiom'?'#6366f1':l==='Practice'?'#22c55e':l==='Tension'?'#f59e0b':'#6b7280';}
function buildElements(){
const nodes=(GRAPH.nodes||[]).map(n=>({data:{id:n.id,label:n.name||n.id,level:n.level,description:n.description||'',color:levelColor(n.level),invariant:n.invariant||false,artifact_paths:n.artifact_paths||[]}}));
const edges=(GRAPH.edges||[]).map((e,i)=>({data:{id:'e'+i,source:e.from,target:e.to,kind:e.kind,note:e.note||'',weight:e.weight}}));
return[...nodes,...edges];
}
function initCytoscape(){
cy=cytoscape({container:document.getElementById('cy'),elements:buildElements(),
style:[
{selector:'node',style:{'background-color':'data(color)','label':'data(label)','color':'#f1f5f9','font-size':'8px','font-family':'ui-monospace,monospace','text-valign':'center','text-halign':'center','text-wrap':'wrap','text-max-width':'56px','text-overflow-wrap':'anywhere','width':72,'height':72,'border-width':2,'border-color':'rgba(255,255,255,0.15)'}},
{selector:'node[?invariant]',style:{'border-width':3,'border-color':'#a5b4fc','border-style':'double'}},
{selector:'edge',style:{'width':1.5,'line-color':'rgba(156,163,175,0.4)','target-arrow-color':'rgba(156,163,175,0.6)','target-arrow-shape':'triangle','curve-style':'bezier','arrow-scale':0.8}},
{selector:'node:selected',style:{'border-color':'#818cf8','border-width':3}},
],
layout:{name:'cose',animate:false,nodeRepulsion:12000,idealEdgeLength:140,gravity:0.15,numIter:1000,fit:true,padding:30}
});
cy.on('tap','node',function(evt){
const d=evt.target.data();
const paths=d.artifact_paths.length?d.artifact_paths.map(p=>`<code class="font-mono">${p}</code>`).join('<br>'):'<span class="text-base-content/30">—</span>';
document.getElementById('node-detail').innerHTML=`<div class="mb-2"><span class="badge badge-xs" style="background:${d.color}">${d.level}</span><span class="font-mono font-semibold ml-1">${d.id}</span></div><p class="font-semibold text-sm mb-1">${d.label}</p><p class="text-base-content/60 mb-3">${d.description}</p><div class="text-base-content/40 text-xs">${paths}</div>`;
});
cy.on('tap',function(evt){if(evt.target===cy)document.getElementById('node-detail').innerHTML='';});
}
function resetLayout(){if(cy)cy.layout({name:'cose',animate:true,nodeRepulsion:12000,idealEdgeLength:140,gravity:0.15,numIter:1000}).run();}
function applyFilters(){
if(!cy)return;
const sa=document.getElementById('show-axioms').checked;
const sp=document.getElementById('show-practices').checked;
const st=document.getElementById('show-tensions').checked;
cy.nodes().forEach(n=>{const l=n.data('level');n.style('display',(l==='Axiom'&&sa)||(l==='Practice'&&sp)||(l==='Tension'&&st)||!['Axiom','Practice','Tension'].includes(l)?'element':'none');});
cy.edges().forEach(e=>{e.style('display',e.source().style('display')!=='none'&&e.target().style('display')!=='none'?'element':'none');});
}
['show-axioms','show-practices','show-tensions'].forEach(id=>{document.getElementById(id).addEventListener('change',applyFilters);});
if(typeof cytoscape!=='undefined'){initCytoscape();}else{const s=document.createElement('script');s.src='https://cdn.jsdelivr.net/npm/cytoscape@3.30.2/dist/cytoscape.min.js';s.onload=initCytoscape;document.head.appendChild(s);}
</script>
{% endblock %}