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

201 lines
9.2 KiB
HTML
Raw Normal View History

{% extends "base.html" %}
{% import "macros/ui.html" as m %}
{% block title %}{{ ws_name }} · Components{% endblock %}
{% block nav_workspaces %}btn-active{% endblock %}
{% block content %}
{{ 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) }}
{% if service_links %}
<div class="flex items-center gap-2 mb-4 flex-wrap">
<span class="text-xs text-base-content/30 font-mono">Quick access:</span>
{% for svc in service_links %}
<a href="{{ svc.url }}" target="_blank" rel="noopener"
class="btn btn-xs btn-ghost border border-base-content/10 font-mono gap-1 text-base-content/60 hover:text-primary">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
{{ svc.label }}
</a>
{% endfor %}
</div>
{% endif %}
{% if components %}
<div class="flex items-center gap-2 mb-3 flex-wrap">
<span class="text-xs text-base-content/40 font-mono">Filter:</span>
<button onclick="filterMode('all')" id="f-all" class="btn btn-xs btn-active" data-filter="all">All</button>
<button onclick="filterMode('taskserv')" id="f-taskserv" class="btn btn-xs btn-ghost" data-filter="taskserv">taskserv</button>
<button onclick="filterMode('cluster')" id="f-cluster" class="btn btn-xs btn-ghost" data-filter="cluster">cluster</button>
<input type="text" id="search-input" oninput="applyFilters()"
placeholder="search…" autocomplete="off"
class="input input-xs input-bordered font-mono w-40 text-xs">
<span class="text-xs text-base-content/30 font-mono" id="count-label">{{ components | length }} component(s)</span>
<div class="flex-1"></div>
<button id="auto-btn" onclick="toggleAuto(this)"
class="btn btn-ghost btn-xs gap-1 font-mono text-base-content/40 hover:text-base-content"
title="Toggle 30s auto-refresh">
<span class="auto-dot w-2 h-2 rounded-full bg-base-content/20 inline-block"></span>
auto
</button>
<button onclick="liveBtnClick(this)" data-live-url="?{% if current_env %}env={{ current_env }}{% endif %}"
class="btn btn-ghost btn-xs gap-1 font-mono text-base-content/40 hover:text-base-content"
title="Reload live component state">
<span class="live-spin loading loading-spinner loading-xs hidden"></span>
<svg class="live-icon w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
<span class="live-label">↺ live</span>
</button>
</div>
<div class="overflow-x-auto">
<table class="table table-sm w-full font-mono" id="comp-table">
<thead>
<tr class="text-xs text-base-content/40 uppercase tracking-wider">
<th class="w-8"></th>
<th>Name</th>
<th>Mode</th>
<th>Target</th>
<th>Namespace</th>
<th>Version</th>
<th>State</th>
<th title="Live state — hcloud status or kubectl pod count">Live</th>
</tr>
</thead>
<tbody>
{% for comp in components %}
<tr class="hover comp-row" data-mode="{{ comp.mode }}" data-name="{{ comp.name }}">
<td class="pr-0">
{% if comp.operations %}
<div class="dropdown dropdown-right">
<button tabindex="0" class="btn btn-ghost btn-xs btn-square opacity-50 hover:opacity-100" title="Actions">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
</button>
<ul tabindex="0" class="dropdown-content menu bg-base-200 shadow-lg rounded-box z-50 w-36 p-1">
{% for op in comp.operations %}
<li>
<a href="/ui/workspaces/{{ ws_name }}/components/{{ comp.name }}#{{ op }}"
class="text-xs gap-2">
<span class="text-base-content/50"></span> {{ op }}
</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
</td>
<td>
<a href="/ui/workspaces/{{ ws_name }}/components/{{ comp.name }}"
class="text-primary font-semibold hover:underline">{{ comp.name }}</a>
</td>
<td>
{% if comp.mode == "taskserv" %}
<span class="badge badge-outline badge-xs">taskserv</span>
{% elif comp.mode == "cluster" %}
<span class="badge badge-info badge-xs">cluster</span>
{% elif comp.mode %}
<span class="badge badge-ghost badge-xs">{{ comp.mode }}</span>
{% else %}
<span class="text-base-content/30"></span>
{% endif %}
</td>
<td class="text-base-content/70">
{% if comp.target %}{{ comp.target }}{% else %}<span class="text-base-content/30"></span>{% endif %}
</td>
<td class="text-base-content/70">
{% if comp.namespace %}{{ comp.namespace }}{% else %}<span class="text-base-content/30"></span>{% endif %}
</td>
<td class="text-base-content/70">
{% if comp.version %}{{ comp.version }}{% else %}<span class="text-base-content/30"></span>{% endif %}
</td>
<td>
{% if comp.ncl_state and comp.ncl_state != "" %}
{% if comp.ncl_state == "completed" %}
<span class="badge badge-success badge-xs">{{ comp.ncl_state }}</span>
{% elif comp.ncl_state == "running" %}
<span class="badge badge-warning badge-xs">{{ comp.ncl_state }}</span>
{% elif comp.ncl_state == "failed" %}
<span class="badge badge-error badge-xs">{{ comp.ncl_state }}</span>
{% else %}
<span class="badge badge-ghost badge-xs">{{ comp.ncl_state }}</span>
{% endif %}
{% else %}
<span class="text-base-content/20"></span>
{% endif %}
</td>
<td>
{% if comp.pod_detail and comp.pod_detail != "" %}
<span class="badge badge-xs font-mono {{ comp.pod_badge }}">{{ comp.pod_detail }}</span>
{% elif comp.live_state and comp.live_state != "" %}
{% if comp.live_state == "running" %}
<span class="badge badge-success badge-xs">running</span>
{% elif comp.live_state == "off" %}
<span class="badge badge-ghost badge-xs">off</span>
{% elif comp.live_state == "starting" or comp.live_state == "initializing" %}
<span class="badge badge-warning badge-xs">{{ comp.live_state }}</span>
{% else %}
<span class="badge badge-ghost badge-xs">{{ comp.live_state }}</span>
{% endif %}
{% else %}
<span class="text-base-content/20"></span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script>
var _activeMode = 'all';
var _autoTimer = null;
function filterMode(mode) {
_activeMode = mode;
document.querySelectorAll('[data-filter]').forEach(b => b.classList.replace('btn-active', 'btn-ghost'));
document.getElementById('f-' + mode).classList.replace('btn-ghost', 'btn-active');
applyFilters();
}
function applyFilters() {
const q = (document.getElementById('search-input')?.value || '').toLowerCase();
let visible = 0;
document.querySelectorAll('.comp-row').forEach(row => {
const modeOk = _activeMode === 'all' || row.dataset.mode === _activeMode;
const name = (row.dataset.name || '').toLowerCase();
const textOk = !q || name.includes(q);
const show = modeOk && textOk;
row.style.display = show ? '' : 'none';
if (show) visible++;
});
document.getElementById('count-label').textContent = visible + ' component(s)';
}
function toggleAuto(btn) {
if (_autoTimer) {
clearInterval(_autoTimer);
_autoTimer = null;
btn.querySelector('.auto-dot').className = 'auto-dot w-2 h-2 rounded-full bg-base-content/20 inline-block';
btn.classList.remove('btn-primary');
} else {
_autoTimer = setInterval(() => location.reload(), 30000);
btn.querySelector('.auto-dot').className = 'auto-dot w-2 h-2 rounded-full bg-success inline-block';
btn.classList.add('btn-primary');
}
}
</script>
{% else %}
<div class="alert">
<span class="text-sm">No components found in this workspace.</span>
</div>
{% endif %}
{% endblock %}