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:
+66
-1
@@ -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) {
|
||||
|
||||
@@ -54,6 +54,11 @@
|
||||
<button class="btn btn-danger btn-sm" id="btn-group-stop" title="Stop all in group">■ Stop All</button>
|
||||
<button class="btn btn-warning btn-sm" id="btn-group-restart" title="Restart all in group">↻ Restart All</button>
|
||||
</div>
|
||||
<div class="toolbar-filters" id="status-filters">
|
||||
<button class="filter-pill active" data-filter="all">All</button>
|
||||
<button class="filter-pill" data-filter="running">Running <span id="count-running" class="filter-count"></span></button>
|
||||
<button class="filter-pill" data-filter="stopped">Stopped <span id="count-stopped" class="filter-count"></span></button>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<input type="text" id="search-input" class="search-input" placeholder="Search services...">
|
||||
</div>
|
||||
|
||||
@@ -344,6 +344,54 @@ body {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.toolbar-filters {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.filter-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.filter-pill:hover {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.filter-pill.active {
|
||||
background: rgba(88, 166, 255, 0.15);
|
||||
color: var(--accent);
|
||||
border-color: rgba(88, 166, 255, 0.3);
|
||||
}
|
||||
|
||||
.filter-pill[data-filter="stopped"].active {
|
||||
background: rgba(248, 81, 73, 0.12);
|
||||
color: var(--danger);
|
||||
border-color: rgba(248, 81, 73, 0.3);
|
||||
}
|
||||
|
||||
.filter-pill[data-filter="running"].active {
|
||||
background: rgba(63, 185, 80, 0.12);
|
||||
color: var(--success);
|
||||
border-color: rgba(63, 185, 80, 0.3);
|
||||
}
|
||||
|
||||
.filter-count {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.toolbar-center {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
@@ -394,6 +442,15 @@ body {
|
||||
box-shadow: 0 0 0 1px rgba(88, 166, 255, 0.15);
|
||||
}
|
||||
|
||||
.service-card.stopped {
|
||||
opacity: 0.55;
|
||||
border-left: 3px solid var(--danger);
|
||||
}
|
||||
|
||||
.service-card.stopped:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
Reference in New Issue
Block a user