126 lines
4.5 KiB
HTML
126 lines
4.5 KiB
HTML
|
|
{% extends "base.html" %}
|
||
|
|
{% import "macros/ui.html" as m %}
|
||
|
|
{% block title %}Workspaces{% endblock %}
|
||
|
|
{% block nav_workspaces %}btn-active{% endblock %}
|
||
|
|
|
||
|
|
{% block content %}
|
||
|
|
<div class="flex items-center gap-3 mb-2">
|
||
|
|
<h1 class="text-2xl font-bold font-mono">Workspaces</h1>
|
||
|
|
<button id="toggle-features-btn" class="btn btn-xs btn-ghost font-mono ml-auto text-base-content/40"
|
||
|
|
onclick="toggleAllFeatures()">features ↕</button>
|
||
|
|
</div>
|
||
|
|
<p class="text-base-content/50 text-sm mb-6">
|
||
|
|
Infrastructure workspaces — only workspaces with a <code class="font-mono">card.ncl</code> are shown.
|
||
|
|
</p>
|
||
|
|
|
||
|
|
{% if not ws_root_set %}
|
||
|
|
<div class="alert alert-warning mb-6">
|
||
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||
|
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-2.194-.833-2.964 0L3.232 16.5c-.77.833.192 2.5 1.732 2.5z"/>
|
||
|
|
</svg>
|
||
|
|
<span>
|
||
|
|
<strong>Workspaces root not set.</strong>
|
||
|
|
Start with <code class="font-mono">--project-root /path/to/provisioning</code>
|
||
|
|
or <code class="font-mono">--workspaces-root /path/to/workspaces</code>.
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
{% if infra_workspaces %}
|
||
|
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3">
|
||
|
|
{% for ws in infra_workspaces %}
|
||
|
|
<div class="card bg-base-200 shadow hover:bg-base-300 transition-colors">
|
||
|
|
<div class="card-body p-4">
|
||
|
|
|
||
|
|
{# ── Header ── #}
|
||
|
|
<div class="flex items-center gap-2">
|
||
|
|
<a href="/ui/workspaces/{{ ws.name }}" class="font-mono font-semibold text-primary text-sm truncate hover:underline">
|
||
|
|
{{ ws.name }}
|
||
|
|
</a>
|
||
|
|
<div class="ml-auto flex items-center gap-1 flex-shrink-0">
|
||
|
|
{% if ws.status == "Active" %}
|
||
|
|
<span class="badge badge-success badge-xs">active</span>
|
||
|
|
{% elif ws.status %}
|
||
|
|
<span class="badge badge-ghost badge-xs font-mono">{{ ws.status | lower }}</span>
|
||
|
|
{% endif %}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{# ── Tagline ── #}
|
||
|
|
{% if ws.tagline %}
|
||
|
|
<p class="text-xs text-base-content/60 mt-1 leading-relaxed">{{ ws.tagline }}</p>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
{# ── Tags ── #}
|
||
|
|
{% if ws.tags %}
|
||
|
|
<div class="flex flex-wrap gap-1 mt-2">
|
||
|
|
{% for tag in ws.tags %}
|
||
|
|
<span class="badge badge-ghost badge-xs font-mono text-base-content/50">{{ tag }}</span>
|
||
|
|
{% endfor %}
|
||
|
|
</div>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
{# ── Environments ── #}
|
||
|
|
{% if ws.environments %}
|
||
|
|
<div class="flex gap-1 flex-wrap mt-2">
|
||
|
|
{% for env in ws.environments %}
|
||
|
|
<span class="badge badge-outline badge-xs font-mono text-primary/60">{{ env }}</span>
|
||
|
|
{% endfor %}
|
||
|
|
</div>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
{# ── Features (collapsible) ── #}
|
||
|
|
{% if ws.features %}
|
||
|
|
<details class="mt-3 ws-features">
|
||
|
|
<summary class="text-xs text-base-content/30 cursor-pointer hover:text-base-content/60 select-none font-mono list-none flex items-center gap-1">
|
||
|
|
<span class="ws-feat-arrow">▶</span>
|
||
|
|
<span>{{ ws.features | length }} features</span>
|
||
|
|
</summary>
|
||
|
|
<ul class="mt-2 space-y-0.5 pl-2 border-l border-base-content/10">
|
||
|
|
{% for feat in ws.features %}
|
||
|
|
<li class="text-xs text-base-content/50 font-mono">{{ feat }}</li>
|
||
|
|
{% endfor %}
|
||
|
|
</ul>
|
||
|
|
</details>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
<div class="mt-3 text-right">
|
||
|
|
<a href="/ui/workspaces/{{ ws.name }}" class="text-xs text-base-content/30 hover:text-primary">View →</a>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
{% endfor %}
|
||
|
|
</div>
|
||
|
|
{% elif ws_root_set %}
|
||
|
|
<div class="alert">
|
||
|
|
<span class="text-sm font-mono">No infra workspaces found (no <code>card.ncl</code> detected).</span>
|
||
|
|
</div>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
<div class="mt-6 flex gap-2">
|
||
|
|
<a href="/ui/tools/workspace_list" class="btn btn-ghost btn-xs font-mono">workspace_list tool</a>
|
||
|
|
<a href="/ui/ontology" class="btn btn-ghost btn-xs">Domain graph</a>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
// Sync the rotate arrow when details open/close.
|
||
|
|
document.querySelectorAll('.ws-features').forEach(d => {
|
||
|
|
d.addEventListener('toggle', () => {
|
||
|
|
const arrow = d.querySelector('.ws-feat-arrow');
|
||
|
|
if (arrow) arrow.textContent = d.open ? '▼' : '▶';
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
let featuresOpen = false;
|
||
|
|
function toggleAllFeatures() {
|
||
|
|
featuresOpen = !featuresOpen;
|
||
|
|
document.querySelectorAll('.ws-features').forEach(d => {
|
||
|
|
d.open = featuresOpen;
|
||
|
|
const arrow = d.querySelector('.ws-feat-arrow');
|
||
|
|
if (arrow) arrow.textContent = featuresOpen ? '▼' : '▶';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
{% endblock %}
|