+
+
-
-
+
+
{service.name || service.id || 'Service'}
-
- Service
-
-
+
Stack: {service.stack}
|
-
+ |
Service
|
-
+ |
{service.image || '—'}
|
-
+ |
{badge.label}
|
-
— |
-
— |
-
+ | — |
+
— |
+
{(service.runningTasks ?? 0)}/{service.desiredTasks ?? 0}
tasks
|
-
+ |
{(timestamp) => (
@@ -663,26 +634,26 @@ const DockerServiceRow: Component<{ row: Extract
-
+
Tasks
{tasks.length} {tasks.length === 1 ? 'entry' : 'entries'}
-
-
-
+
+
+
- | Task |
- Node |
- State |
- CPU |
- Memory |
- Updated |
-
-
-
+ Task |
+ Node |
+ State |
+ CPU |
+ Memory |
+ Updated |
+
+
+
{(task) => {
const container = findContainerForTask(host.containers || [], task);
@@ -695,6 +666,16 @@ const DockerServiceRow: Component<{ row: Extract
if (task.slot !== undefined) return `slot-${task.slot}`;
return task.id ?? 'Task';
};
+ const taskTitle = () => {
+ const label = taskLabel();
+ if (task.containerId && task.containerId !== label) {
+ return `${label} \u2014 ${task.containerId}`;
+ }
+ if (task.id && task.id !== label) {
+ return `${label} \u2014 ${task.id}`;
+ }
+ return label;
+ };
const state = toLower(task.currentState ?? task.desiredState ?? 'unknown');
const stateClass = () => {
if (state === 'running') {
@@ -706,10 +687,10 @@ const DockerServiceRow: Component<{ row: Extract
return 'bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-300';
};
return (
-
+
-
+
{taskLabel()}
= (props) => {
return;
}
- if (selectedHostId && host.id !== selectedHostId) {
+ if (selectedHostId && host.id !== selectedHostId) {
return;
}
- const hostRows: DockerRow[] = [];
+ const containerRows: Array> = [];
+ const serviceRows: Array> = [];
+
const containers = host.containers || [];
const services = host.services || [];
const tasks = host.tasks || [];
+ const serviceNames = new Set();
+ const serviceIds = new Set();
+ services.forEach((service) => {
+ if (service.name) serviceNames.add(service.name.toLowerCase());
+ if (service.id) serviceIds.add(service.id.toLowerCase());
+ });
+
+ const serviceOwnedContainers = new Set();
+
containers.forEach((container) => {
if (!containerMatchesStateFilter(filter, container)) return;
const matchesSearch = searchTokens.every((token) => containerMatchesToken(token, host, container));
if (!matchesSearch) return;
- hostRows.push({
+ containerRows.push({
kind: 'container',
id: container.id || `${host.id}-container-${container.name}`,
host,
@@ -823,7 +815,12 @@ const DockerUnifiedTable: Component = (props) => {
return false;
});
- hostRows.push({
+ associatedTasks.forEach((task) => {
+ if (task.containerId) serviceOwnedContainers.add(task.containerId.toLowerCase());
+ if (task.containerName) serviceOwnedContainers.add(task.containerName.toLowerCase());
+ });
+
+ serviceRows.push({
kind: 'service',
id: service.id || `${host.id}-service-${service.name}`,
host,
@@ -832,18 +829,48 @@ const DockerUnifiedTable: Component = (props) => {
});
});
- if (hostRows.length > 0) {
- hostRows.sort((a, b) => {
- const nameA =
- a.kind === 'container'
- ? a.container.name || a.container.id || ''
- : a.service.name || a.service.id || '';
- const nameB =
- b.kind === 'container'
- ? b.container.name || b.container.id || ''
- : b.service.name || b.service.id || '';
+ if (serviceRows.length > 0) {
+ serviceRows.sort((a, b) => {
+ const nameA = a.service.name || a.service.id || '';
+ const nameB = b.service.name || b.service.id || '';
return nameA.localeCompare(nameB);
});
+ }
+
+ if (containerRows.length > 0) {
+ containerRows.sort((a, b) => {
+ const nameA = a.container.name || a.container.id || '';
+ const nameB = b.container.name || b.container.id || '';
+ return nameA.localeCompare(nameB);
+ });
+ const filtered = containerRows.filter((row) => {
+ const idKey = (row.container.id || '').toLowerCase();
+ const nameKey = (row.container.name || '').toLowerCase();
+ const shortNameKey = nameKey.split('.')[0];
+
+ const labelServiceName =
+ row.container.labels?.['com.docker.swarm.service.name']?.toLowerCase() ?? '';
+ const labelServiceID =
+ row.container.labels?.['com.docker.swarm.service.id']?.toLowerCase() ?? '';
+
+ const belongsToService =
+ (idKey && serviceOwnedContainers.has(idKey)) ||
+ (nameKey && serviceOwnedContainers.has(nameKey)) ||
+ (shortNameKey && serviceOwnedContainers.has(shortNameKey)) ||
+ (labelServiceName && serviceNames.has(labelServiceName)) ||
+ (labelServiceID && serviceIds.has(labelServiceID)) ||
+ (serviceNames.size > 0 && nameKey && [...serviceNames].some((svc) => nameKey.startsWith(`${svc}.`)));
+
+ return !belongsToService;
+ });
+
+ containerRows.length = 0;
+ containerRows.push(...filtered);
+ }
+
+ const hostRows = [...serviceRows, ...containerRows];
+
+ if (hostRows.length > 0) {
groups.push({ host, rows: hostRows });
}
});
@@ -908,13 +935,13 @@ const DockerUnifiedTable: Component = (props) => {
- |
+ |
Resource
|
Type
|
-
+ |
Image / Stack
|
| | | |