feat(dashboard): add status filter pills and dim stopped services

- Add All/Running/Stopped filter pills with live counts in toolbar
- Dim stopped service cards (55% opacity, red left border)
- Cards brighten on hover for easy interaction

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 07:30:10 -06:00
parent fe76ca7456
commit 04c2e1f102
3 changed files with 128 additions and 1 deletions
+66 -1
View File
@@ -12,6 +12,7 @@ let logPanelOpen = false;
let modalOpen = false;
let editingGroupId = null; // null = creating, string = editing
let searchQuery = "";
let statusFilter = "all"; // "all", "running", "stopped"
// ---------------------------------------------------------------------------
// DOM references
@@ -199,6 +200,39 @@ function renderMain() {
renderServices(filtered);
}
function getPreStatusFilteredServices() {
let list = [...services];
if (currentView === "all") {
// show all
} else if (currentView.startsWith("node:")) {
const nodeName = currentView.slice(5);
list = list.filter((s) => s.node === nodeName);
} else {
const group = groups.find((g) => g.id === currentView);
if (group) {
const svcSet = new Set(
group.services.map((s) => s.node + ":" + s.container)
);
list = list.filter((s) => svcSet.has(s.node + ":" + s.name));
} else {
list = [];
}
}
if (searchQuery) {
const q = searchQuery.toLowerCase();
list = list.filter(
(s) =>
s.name.toLowerCase().includes(q) ||
s.image.toLowerCase().includes(q) ||
s.node.toLowerCase().includes(q)
);
}
return list;
}
function getFilteredServices() {
let list = [...services];
@@ -232,6 +266,13 @@ function getFilteredServices() {
);
}
// Status filter
if (statusFilter === "running") {
list = list.filter((s) => s.status === "running");
} else if (statusFilter === "stopped") {
list = list.filter((s) => s.status !== "running");
}
return list;
}
@@ -251,6 +292,20 @@ function renderToolbar(filtered) {
const countEl = $("#service-count");
countEl.textContent = filtered.length + " service" + (filtered.length !== 1 ? "s" : "");
// Status filter counts (from pre-status-filtered list)
const preFiltered = getPreStatusFilteredServices();
const runningCount = preFiltered.filter((s) => s.status === "running").length;
const stoppedCount = preFiltered.filter((s) => s.status !== "running").length;
const countRunEl = $("#count-running");
const countStopEl = $("#count-stopped");
if (countRunEl) countRunEl.textContent = runningCount;
if (countStopEl) countStopEl.textContent = stoppedCount;
// Update active pill
$$(".filter-pill").forEach((pill) => {
pill.classList.toggle("active", pill.dataset.filter === statusFilter);
});
// Group actions visibility
const groupActions = $("#group-actions");
const isGroupView =
@@ -311,8 +366,10 @@ function renderServiceCard(s) {
? `<span class="card-swarm-badge">Swarm</span>`
: "";
const stoppedClass = !isRunning ? " stopped" : "";
return `
<div class="service-card">
<div class="service-card${stoppedClass}">
<div class="card-header">
<div>
<span class="card-name">${escHtml(s.name)}</span>
@@ -685,6 +742,14 @@ function bindEvents() {
});
}
// Status filter pills
$$(".filter-pill").forEach((pill) => {
pill.addEventListener("click", () => {
statusFilter = pill.dataset.filter;
renderMain();
});
});
// Search
const searchInput = $("#search-input");
if (searchInput) {