177 lines
5.5 KiB
HTML
177 lines
5.5 KiB
HTML
{#
|
|
Cytoscape.js DAG visualization component.
|
|
|
|
Usage:
|
|
{% from "components/dag.html" import dag_container, dag_scripts, dag_legend %}
|
|
|
|
{# In head block #}
|
|
{{ dag_scripts() }}
|
|
|
|
{# In content #}
|
|
{{ dag_container(id="plan-dag", height="400px") }}
|
|
{{ dag_legend() }}
|
|
|
|
{# In scripts block #}
|
|
<script>
|
|
initDag('plan-dag', {{ nodes | tojson }}, {{ edges | tojson }});
|
|
</script>
|
|
#}
|
|
|
|
{% macro dag_scripts() %}
|
|
<script src="{{ CYTOSCAPE_CDN }}"></script>
|
|
<script src="{{ DAGRE_CDN }}"></script>
|
|
<script src="{{ CYTOSCAPE_DAGRE_CDN }}"></script>
|
|
<script>
|
|
// Global Cytoscape instance for WebSocket updates
|
|
window.artdagCy = null;
|
|
|
|
function initDag(containerId, nodes, edges) {
|
|
const nodeColors = {{ NODE_COLORS | tojson }};
|
|
|
|
window.artdagCy = cytoscape({
|
|
container: document.getElementById(containerId),
|
|
elements: {
|
|
nodes: nodes,
|
|
edges: edges
|
|
},
|
|
style: [
|
|
{
|
|
selector: 'node',
|
|
style: {
|
|
'label': 'data(label)',
|
|
'text-valign': 'center',
|
|
'text-halign': 'center',
|
|
'background-color': function(ele) {
|
|
return nodeColors[ele.data('nodeType')] || nodeColors['default'];
|
|
},
|
|
'color': '#fff',
|
|
'font-size': '10px',
|
|
'width': 80,
|
|
'height': 40,
|
|
'shape': 'round-rectangle',
|
|
'text-wrap': 'wrap',
|
|
'text-max-width': '70px',
|
|
}
|
|
},
|
|
{
|
|
selector: 'node[status="cached"], node[status="completed"]',
|
|
style: {
|
|
'border-width': 3,
|
|
'border-color': '#22c55e'
|
|
}
|
|
},
|
|
{
|
|
selector: 'node[status="running"]',
|
|
style: {
|
|
'border-width': 3,
|
|
'border-color': '#eab308',
|
|
'border-style': 'dashed'
|
|
}
|
|
},
|
|
{
|
|
selector: 'node:selected',
|
|
style: {
|
|
'border-width': 3,
|
|
'border-color': '#3b82f6'
|
|
}
|
|
},
|
|
{
|
|
selector: 'edge',
|
|
style: {
|
|
'width': 2,
|
|
'line-color': '#6b7280',
|
|
'target-arrow-color': '#6b7280',
|
|
'target-arrow-shape': 'triangle',
|
|
'curve-style': 'bezier'
|
|
}
|
|
}
|
|
],
|
|
layout: {
|
|
name: 'dagre',
|
|
rankDir: 'TB',
|
|
nodeSep: 50,
|
|
rankSep: 80,
|
|
padding: 20
|
|
},
|
|
userZoomingEnabled: true,
|
|
userPanningEnabled: true,
|
|
boxSelectionEnabled: false
|
|
});
|
|
|
|
// Click handler for node details
|
|
window.artdagCy.on('tap', 'node', function(evt) {
|
|
const node = evt.target;
|
|
const data = node.data();
|
|
showNodeDetails(data);
|
|
});
|
|
|
|
return window.artdagCy;
|
|
}
|
|
|
|
function showNodeDetails(data) {
|
|
const panel = document.getElementById('node-details');
|
|
if (!panel) return;
|
|
|
|
panel.innerHTML = `
|
|
<h4 class="font-medium text-white mb-2">${data.label || data.id}</h4>
|
|
<dl class="space-y-1 text-sm">
|
|
<div class="flex justify-between">
|
|
<dt class="text-gray-400">Type</dt>
|
|
<dd class="text-white">${data.nodeType || 'Unknown'}</dd>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<dt class="text-gray-400">Status</dt>
|
|
<dd class="text-white">${data.status || 'pending'}</dd>
|
|
</div>
|
|
${data.cacheId ? `
|
|
<div class="flex justify-between">
|
|
<dt class="text-gray-400">Cache ID</dt>
|
|
<dd class="text-white font-mono text-xs">${data.cacheId.substring(0, 16)}...</dd>
|
|
</div>
|
|
` : ''}
|
|
${data.level !== undefined ? `
|
|
<div class="flex justify-between">
|
|
<dt class="text-gray-400">Level</dt>
|
|
<dd class="text-white">${data.level}</dd>
|
|
</div>
|
|
` : ''}
|
|
</dl>
|
|
`;
|
|
panel.classList.remove('hidden');
|
|
}
|
|
|
|
// Future WebSocket support: update node status in real-time
|
|
function updateNodeStatus(stepId, status, cacheId) {
|
|
if (!window.artdagCy) return;
|
|
const node = window.artdagCy.getElementById(stepId);
|
|
if (node && node.length > 0) {
|
|
node.data('status', status);
|
|
if (cacheId) {
|
|
node.data('cacheId', cacheId);
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
{% endmacro %}
|
|
|
|
{% macro dag_container(id="dag-container", height="400px", class="") %}
|
|
<div id="{{ id }}" class="w-full bg-dark-700 rounded-lg {{ class }}" style="height: {{ height }};"></div>
|
|
<div id="node-details" class="hidden mt-4 p-4 bg-dark-600 rounded-lg"></div>
|
|
{% endmacro %}
|
|
|
|
{% macro dag_legend(node_types=None) %}
|
|
{% set types = node_types or ["SOURCE", "EFFECT", "_LIST"] %}
|
|
<div class="flex gap-4 text-sm flex-wrap mt-4">
|
|
{% for type in types %}
|
|
<span class="flex items-center gap-2">
|
|
<span class="w-4 h-4 rounded" style="background-color: {{ NODE_COLORS.get(type, NODE_COLORS.default) }}"></span>
|
|
{{ type }}
|
|
</span>
|
|
{% endfor %}
|
|
<span class="flex items-center gap-2">
|
|
<span class="w-4 h-4 rounded border-2 border-green-500 bg-dark-600"></span>
|
|
Cached
|
|
</span>
|
|
</div>
|
|
{% endmacro %}
|