Skip to content

Commit

Permalink
Put tasks onto Network display to include IPs
Browse files Browse the repository at this point in the history
  • Loading branch information
YaytayAtWork committed Nov 27, 2024
1 parent d48bb77 commit f498ecc
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 7 deletions.
61 changes: 59 additions & 2 deletions src/Network.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ import { useParams } from 'react-router';
import JSONPretty from 'react-json-pretty';
import 'react-json-pretty/themes/monikai.css';
import Box from '@mui/material/Box';
import { Network } from './docker-schema';
import { Network, Service } from './docker-schema';
import Section from './Section'
import Grid from '@mui/material/Grid2';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import { DockerApi } from './DockerApi';
import { ContainerData, DockerApi } from './DockerApi';
import KeyValueTable from './KeyValueTable';
import LabelsTable, { createLabelDetails, LabelDetails } from './tables/LabelsTable';
import ServicesTable, { createServiceDetails, ServiceDetails } from './tables/ServicesTable';
import IpamConfigsTable, { createIpamConfigDetails, IpamConfigDetails } from './tables/IpamConfigsTable';
import NetworkTasksTable, { createNetworkTaskDetails, NetworkTaskDetails } from './tables/NetworkTasksTable';


interface NetworkProps {
Expand All @@ -35,15 +36,27 @@ function NetworkUi(props: NetworkProps) {
const [serviceDetails, setServiceDetails] = useState<ServiceDetails[]>([])
const [ipamDetails, setIpamDetails] = useState<IpamConfigDetails[]>([])

const [taskDetails, setTaskDetails] = useState<NetworkTaskDetails[]>([])

useEffect(() => {
Promise.all([
props.docker.networks()
, props.docker.services()
, props.docker.exposedPorts()
, props.docker.nodesById()
]).then(value => {
const nets = value[0]
const services = value[1]
const exposedPorts = value[2]
const nodesById = value[3]

// This repetition of docker.servicesById() avoids a race condition where that hits the network twice
const servicesById = services.reduce((result, current) => {
if (current.ID) {
result.set(current.ID, current)
}
return result
}, new Map<string, Service>())

const net = nets.find(net => { return net.Id === id })
setNetwork(net)
Expand Down Expand Up @@ -80,6 +93,46 @@ function NetworkUi(props: NetworkProps) {
} else {
setIpamDetails([])
}

if (net) {
props.docker.tasks()
.then(tsks => {
return tsks.filter(tsk => {
return tsk.Status?.State == 'running'
&& tsk.Spec?.Networks
&& tsk.Spec?.Networks?.findIndex(tsknet => tsknet.Target === id ) >= 0
})
})
.then(tsks => {
console.log('Tasks for this network: ', tsks)
const ctrPromises = [] as Promise<ContainerData>[]
tsks.forEach(tsk => {
if (tsk.NodeID && tsk.ID) {
ctrPromises.push(props.docker.container(tsk.NodeID, tsk.ID))
}
})
Promise.all(ctrPromises)
.then(ctrs => {
const nowMs = Date.now()
setTaskDetails(
tsks.reduce((result, current) => {
result.push(
createNetworkTaskDetails(
net
, current
, servicesById
, nodesById
, exposedPorts
, ctrs
, nowMs
)
)
return result
}, [] as NetworkTaskDetails[])
)
})
})
}
})
}
, [props, id])
Expand Down Expand Up @@ -138,6 +191,10 @@ function NetworkUi(props: NetworkProps) {
<Section id="network.services" heading="Services" xs={12} >
<ServicesTable id="network.services.table" services={serviceDetails} />
</Section>

<Section id="network.tasks" heading="Tasks" xs={12} >
<NetworkTasksTable id="network.tasks.table" tasks={taskDetails} />
</Section>
</Grid>
</Box>
</Box>
Expand Down
10 changes: 6 additions & 4 deletions src/Task.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ function TaskUi(props: TaskUiProps) {
const nodesById = value[3]
const exposedPorts = value[4]

const task = tasks.find(tsk => { return tsk.ID === id })
setTask(task)
props.setTitle('Task: ' + (task ? ((task.ServiceID && servicesById.get(task.ServiceID)?.Spec?.Name) ?? task.ServiceID) + '.' + (task.Slot ? task.Slot : task.NodeID) : id))
const tsk = tasks.find(tsk => { return tsk.ID === id })
setTask(tsk)
const title = 'Task: ' + (tsk ? ((tsk.ServiceID && servicesById.get(tsk.ServiceID)?.Spec?.Name) ?? tsk.ServiceID) + '.' + (tsk.Slot ? tsk.Slot : tsk.NodeID) : id)
console.log(title)
props.setTitle(title)
setNodes(nodesById)


Expand Down Expand Up @@ -155,7 +157,7 @@ function TaskUi(props: TaskUiProps) {
})
setResources(buildResources)

if (task && task.ID && task.NodeID) {
if (task && task.ID && task.NodeID && (task.Status?.State == 'running')) {
Promise.all([
props.docker.container(task.NodeID, task.ID)
, props.docker.system(task.NodeID)
Expand Down
152 changes: 152 additions & 0 deletions src/tables/NetworkTasksTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { MRT_ColumnDef } from 'material-react-table';
import MaterialTable, { MaterialTableState } from '../MaterialTable';
import { Link } from 'react-router-dom';
import * as duration from 'duration-fns'
import { Dimensions } from '../app-types';
import { Service, Task, Node, Network } from '../docker-schema';
import { ContainerData } from '../DockerApi';

export interface NetworkTaskDetails {
id: string
name: string
stack: string
service: string
serviceId: string
node: string
nodeId: string
created?: string
age: number
aliases: string
address: string
ports?: string
}

const taskColumns: MRT_ColumnDef<NetworkTaskDetails>[] = [
{
accessorKey: 'name',
header: 'NAME',
size: 220,
Cell: ({ renderedCellValue, row }) => (<Link to={"/task/" + row.original.id} >{renderedCellValue}</Link>)
},
{
accessorKey: 'stack',
header: 'STACK',
size: 180,
Cell: ({ renderedCellValue, row }) => (<Link to={"/stack/" + row.original.stack} >{renderedCellValue}</Link>)
},
{
accessorKey: 'service',
header: 'SERVICE',
size: 230,
Cell: ({ renderedCellValue, row }) => (<Link to={"/service/" + row.original.serviceId} >{renderedCellValue}</Link>)
},
{
accessorKey: 'node',
header: 'NODE',
size: 230,
Cell: ({ renderedCellValue, row }) => (<Link to={"/node/" + row.original.nodeId} >{renderedCellValue}</Link>)
},
{
accessorKey: 'created',
header: 'CREATED',
size: 180,
},
{
accessorKey: 'age',
header: 'AGE',
size: 180,
Cell: ({ row }) => (duration.toString(duration.normalize(row.original.age * 1000)))
},
{
accessorKey: 'aliases',
header: 'ALIASES',
size: 600,
},
{
accessorKey: 'address',
header: 'ADDRESS',
size: 100,
},
{
accessorKey: 'ports',
header: 'PORTS',
filterVariant: 'select',
size: 300,
Cell: ({ renderedCellValue }) => <div className="text-wrap">{renderedCellValue}</div>
},
]

const defaultState: MaterialTableState = {
columnFilters: []
, columnOrder: taskColumns.map((c) => c.accessorKey as string)
, columnVisibility: { error: false, memory: false, created: false }
, columnSizing: {}
, density: 'compact'
, showColumnFilters: false
, showGlobalFilter: false
, sorting: []
}

interface NetworkTasksTableProps {
id: string
tasks: NetworkTaskDetails[]
border?: boolean
maxSize?: Dimensions
}
function NetworkTasksTable(props: NetworkTasksTableProps) {
return (
<MaterialTable
id={props.id}
columns={taskColumns}
data={props.tasks}
border={props.border}
virtual={true}
defaultState={defaultState}
muiTableContainerProps={props.maxSize ? { sx: { maxHeight: props.maxSize.height + 'px', maxWidth: props.maxSize.width + 'px' } } : {}}
/>
)
}

export function createNetworkTaskDetails(net: Network
, task: Task
, servicesById: Map<string, Service>
, nodesById: Map<string, Node>
, exposedPorts: Record<string, string[]>
, containers: ContainerData[]
, nowMs: number
): NetworkTaskDetails {
const ports = (task.Status?.PortStatus?.Ports?.map(portSpec => {
return portSpec.PublishedPort + ':' + portSpec.TargetPort
}) || []).concat(
task.Spec?.ContainerSpec?.Image ? exposedPorts[task.Spec.ContainerSpec.Image.replace(/:.*@/, "@")] : []
).filter(Boolean).join(', ')
const age = task.CreatedAt ? ~~((nowMs - new Date(task.CreatedAt).getTime()) / 1000) : 0

const stack = task.Spec?.ContainerSpec?.Labels && task.Spec?.ContainerSpec?.Labels['com.docker.stack.namespace'] || ''
const service = (servicesById && task.ServiceID) ? servicesById.get(task.ServiceID)?.Spec?.Name || '' : ''
const name = service + '.' + (task.Slot || task.NodeID)

const node = (nodesById && task.NodeID) ? nodesById.get(task.NodeID)?.Description?.Hostname || task.NodeID : task.NodeID || ''

const ctr = containers?.find(ctr => ctr.container?.Config?.Labels?.['com.docker.swarm.task.id'] === task.ID)

return {
id: task.ID || ''
, name: name
, stack: stack
, service: service
, serviceId: task.ServiceID || ''
, node: node
, nodeId: task.NodeID || ''
, created: task.CreatedAt || ''
, age: age
, ports: ports
, aliases: ([] as string[])
.concat(ctr?.container?.NetworkSettings?.Networks?.[net?.Name || '']?.Aliases || [])
.concat(ctr?.container?.NetworkSettings?.Networks?.[net?.Name || '']?.DNSNames || [])
.join(', ')
, address: ctr?.container?.NetworkSettings?.Networks?.[net?.Name || '']?.IPAddress ||''
}
}

export default NetworkTasksTable;
2 changes: 1 addition & 1 deletion src/tables/TasksTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const taskColumns: MRT_ColumnDef<TaskDetails>[] = [
accessorKey: 'node',
header: 'NODE',
size: 230,
Cell: ({ renderedCellValue, row }) => (<Link to={"/node/" + row.original.serviceId} >{renderedCellValue}</Link>)
Cell: ({ renderedCellValue, row }) => (<Link to={"/node/" + row.original.nodeId} >{renderedCellValue}</Link>)
},
{
accessorKey: 'created',
Expand Down

0 comments on commit f498ecc

Please sign in to comment.