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

200 lines
9.2 KiB
HTML
Raw Permalink 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 }} · 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 %}