sward resources use list items

This commit is contained in:
mbecker20
2025-11-22 02:41:49 -08:00
parent 73285c4374
commit 8341c6b802
19 changed files with 493 additions and 72 deletions

View File

@@ -64,6 +64,8 @@ fn convert_node_list_item(
state: node
.status
.and_then(|status| status.state.map(convert_state)),
created_at: node.created_at,
updated_at: node.updated_at,
}
}

View File

@@ -1,7 +1,7 @@
use anyhow::Context;
use bollard::query_parameters::ListSecretsOptions;
use komodo_client::entities::docker::secret::{
SecretSpec, SwarmSecret,
SecretSpec, SwarmSecret, SwarmSecretListItem,
};
use super::DockerClient;
@@ -9,14 +9,14 @@ use super::DockerClient;
impl DockerClient {
pub async fn list_swarm_secrets(
&self,
) -> anyhow::Result<Vec<SwarmSecret>> {
) -> anyhow::Result<Vec<SwarmSecretListItem>> {
let secrets = self
.docker
.list_secrets(Option::<ListSecretsOptions>::None)
.await
.context("Failed to query for swarm secret list")?
.into_iter()
.map(convert_secret)
.map(convert_secret_list_item)
.collect();
Ok(secrets)
}
@@ -38,6 +38,29 @@ impl DockerClient {
}
}
fn convert_secret_list_item(
secret: bollard::models::Secret,
) -> SwarmSecretListItem {
let (name, driver, templating) = secret
.spec
.map(|spec| {
(
spec.name,
spec.driver.map(|driver| driver.name),
spec.templating.map(|driver| driver.name),
)
})
.unwrap_or_default();
SwarmSecretListItem {
id: secret.id,
name,
driver,
templating,
created_at: secret.created_at,
updated_at: secret.updated_at,
}
}
fn convert_secret(secret: bollard::models::Secret) -> SwarmSecret {
SwarmSecret {
id: secret.id,

View File

@@ -81,6 +81,8 @@ fn convert_service_list_item(
image,
restart,
runtime,
created_at: service.created_at,
updated_at: service.updated_at,
}
}

View File

@@ -7,14 +7,14 @@ use super::*;
impl DockerClient {
pub async fn list_swarm_tasks(
&self,
) -> anyhow::Result<Vec<SwarmTask>> {
) -> anyhow::Result<Vec<SwarmTaskListItem>> {
let tasks = self
.docker
.list_tasks(Option::<ListTasksOptions>::None)
.await
.context("Failed to query for swarm tasks list")?
.into_iter()
.map(convert_task)
.map(convert_task_list_item)
.collect();
Ok(tasks)
}
@@ -34,6 +34,33 @@ impl DockerClient {
}
}
fn convert_task_list_item(
task: bollard::models::Task,
) -> SwarmTaskListItem {
let (container_id, state) = task
.status
.map(|status| {
(
status
.container_status
.and_then(|status| status.container_id),
status.state.map(convert_task_state),
)
})
.unwrap_or_default();
SwarmTaskListItem {
id: task.id,
name: task.name,
node_id: task.node_id,
service_id: task.service_id,
container_id,
state,
desired_state: task.desired_state.map(convert_task_state),
created_at: task.created_at,
updated_at: task.updated_at,
}
}
fn convert_task(task: bollard::models::Task) -> SwarmTask {
SwarmTask {
id: task.id,

View File

@@ -7,10 +7,10 @@ use crate::entities::{
docker::{
config::{SwarmConfig, SwarmConfigListItem},
node::{SwarmNode, SwarmNodeListItem},
secret::SwarmSecret,
secret::{SwarmSecret, SwarmSecretListItem},
service::{SwarmService, SwarmServiceListItem},
swarm::SwarmInspectInfo,
task::SwarmTask,
task::{SwarmTask, SwarmTaskListItem},
},
swarm::{Swarm, SwarmActionState, SwarmListItem, SwarmQuery},
};
@@ -242,7 +242,7 @@ pub struct ListSwarmTasks {
}
#[typeshare]
pub type ListSwarmTasksResponse = Vec<SwarmTask>;
pub type ListSwarmTasksResponse = Vec<SwarmTaskListItem>;
//
@@ -284,7 +284,7 @@ pub struct ListSwarmSecrets {
}
#[typeshare]
pub type ListSwarmSecretsResponse = Vec<SwarmSecret>;
pub type ListSwarmSecretsResponse = Vec<SwarmSecretListItem>;
//

View File

@@ -7,8 +7,8 @@ use crate::entities::{
docker::{
config::SwarmConfigListItem, container::ContainerListItem,
image::ImageListItem, network::NetworkListItem,
node::SwarmNodeListItem, secret::SwarmSecret,
service::SwarmServiceListItem, task::SwarmTask,
node::SwarmNodeListItem, secret::SwarmSecretListItem,
service::SwarmServiceListItem, task::SwarmTaskListItem,
volume::VolumeListItem,
},
stack::ComposeProject,
@@ -33,8 +33,8 @@ pub mod volume;
pub struct SwarmLists {
pub nodes: Vec<SwarmNodeListItem>,
pub services: Vec<SwarmServiceListItem>,
pub tasks: Vec<SwarmTask>,
pub secrets: Vec<SwarmSecret>,
pub tasks: Vec<SwarmTaskListItem>,
pub secrets: Vec<SwarmSecretListItem>,
pub configs: Vec<SwarmConfigListItem>,
}

View File

@@ -34,6 +34,14 @@ pub struct SwarmNodeListItem {
/// State of the node
#[serde(rename = "State")]
pub state: Option<NodeState>,
/// Date and time at which the node was added to the swarm in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds.
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
/// Date and time at which the node was last updated in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds.
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
}
/// Swarm node details.

View File

@@ -5,6 +5,35 @@ use typeshare::typeshare;
use super::*;
/// Swarm secret list item.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmSecretListItem {
#[serde(rename = "ID")]
pub id: Option<String>,
/// User-defined name of the secret.
#[serde(rename = "Name")]
pub name: Option<String>,
/// Name of the secrets driver used to fetch the secret's value from an external secret store.
#[serde(rename = "Driver")]
pub driver: Option<String>,
/// Templating driver, if applicable Templating controls whether and how to evaluate the config payload as a template.
/// If no driver is set, no templating is used.
#[serde(rename = "Templating")]
pub templating: Option<String>,
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
}
/// Swarm secret details.
#[typeshare]
#[derive(

View File

@@ -40,6 +40,12 @@ pub struct SwarmServiceListItem {
/// Number of replicas
#[serde(rename = "Replicas")]
pub replicas: Option<I64>,
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
}
/// Swarm service details.

View File

@@ -3,6 +3,46 @@ use typeshare::typeshare;
use super::*;
/// Swarm task list item.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmTaskListItem {
/// The ID of the task.
#[serde(rename = "ID")]
pub id: Option<String>,
/// Name of the task.
#[serde(rename = "Name")]
pub name: Option<String>,
/// The ID of the node that this task is on.
#[serde(rename = "NodeID")]
pub node_id: Option<String>,
/// The ID of the service this task is part of.
#[serde(rename = "ServiceID")]
pub service_id: Option<String>,
/// The ID of container associated with this task.
#[serde(rename = "ContainerID")]
pub container_id: Option<String>,
#[serde(rename = "State")]
pub state: Option<TaskState>,
#[serde(rename = "DesiredState")]
pub desired_state: Option<TaskState>,
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
}
/// Swarm task details.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,

View File

@@ -4372,6 +4372,7 @@ export interface TaskStatus {
PortStatus?: PortStatus;
}
/** Swarm task details. */
export interface SwarmTask {
/** The ID of the task. */
ID?: string;
@@ -5003,11 +5004,31 @@ export interface SwarmNodeListItem {
Availability?: NodeSpecAvailabilityEnum;
/** State of the node */
State?: NodeState;
/** Date and time at which the node was added to the swarm in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. */
CreatedAt?: string;
/** Date and time at which the node was last updated in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. */
UpdatedAt?: string;
}
export type ListSwarmNodesResponse = SwarmNodeListItem[];
export type ListSwarmSecretsResponse = SwarmSecret[];
/** Swarm secret list item. */
export interface SwarmSecretListItem {
ID?: string;
/** User-defined name of the secret. */
Name?: string;
/** Name of the secrets driver used to fetch the secret's value from an external secret store. */
Driver?: string;
/**
* Templating driver, if applicable Templating controls whether and how to evaluate the config payload as a template.
* If no driver is set, no templating is used.
*/
Templating?: string;
CreatedAt?: string;
UpdatedAt?: string;
}
export type ListSwarmSecretsResponse = SwarmSecretListItem[];
/** Swarm service list item. */
export interface SwarmServiceListItem {
@@ -5022,11 +5043,31 @@ export interface SwarmServiceListItem {
Restart?: TaskSpecRestartPolicyConditionEnum;
/** Number of replicas */
Replicas?: I64;
CreatedAt?: string;
UpdatedAt?: string;
}
export type ListSwarmServicesResponse = SwarmServiceListItem[];
export type ListSwarmTasksResponse = SwarmTask[];
/** Swarm task list item. */
export interface SwarmTaskListItem {
/** The ID of the task. */
ID?: string;
/** Name of the task. */
Name?: string;
/** The ID of the node that this task is on. */
NodeID?: string;
/** The ID of the service this task is part of. */
ServiceID?: string;
/** The ID of container associated with this task. */
ContainerID?: string;
State?: TaskState;
DesiredState?: TaskState;
CreatedAt?: string;
UpdatedAt?: string;
}
export type ListSwarmTasksResponse = SwarmTaskListItem[];
export enum SwarmState {
/** Unknown case */

View File

@@ -4324,6 +4324,7 @@ export interface TaskStatus {
ContainerStatus?: ContainerStatus;
PortStatus?: PortStatus;
}
/** Swarm task details. */
export interface SwarmTask {
/** The ID of the task. */
ID?: string;
@@ -4878,9 +4879,28 @@ export interface SwarmNodeListItem {
Availability?: NodeSpecAvailabilityEnum;
/** State of the node */
State?: NodeState;
/** Date and time at which the node was added to the swarm in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. */
CreatedAt?: string;
/** Date and time at which the node was last updated in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. */
UpdatedAt?: string;
}
export type ListSwarmNodesResponse = SwarmNodeListItem[];
export type ListSwarmSecretsResponse = SwarmSecret[];
/** Swarm secret list item. */
export interface SwarmSecretListItem {
ID?: string;
/** User-defined name of the secret. */
Name?: string;
/** Name of the secrets driver used to fetch the secret's value from an external secret store. */
Driver?: string;
/**
* Templating driver, if applicable Templating controls whether and how to evaluate the config payload as a template.
* If no driver is set, no templating is used.
*/
Templating?: string;
CreatedAt?: string;
UpdatedAt?: string;
}
export type ListSwarmSecretsResponse = SwarmSecretListItem[];
/** Swarm service list item. */
export interface SwarmServiceListItem {
ID?: string;
@@ -4894,9 +4914,28 @@ export interface SwarmServiceListItem {
Restart?: TaskSpecRestartPolicyConditionEnum;
/** Number of replicas */
Replicas?: I64;
CreatedAt?: string;
UpdatedAt?: string;
}
export type ListSwarmServicesResponse = SwarmServiceListItem[];
export type ListSwarmTasksResponse = SwarmTask[];
/** Swarm task list item. */
export interface SwarmTaskListItem {
/** The ID of the task. */
ID?: string;
/** Name of the task. */
Name?: string;
/** The ID of the node that this task is on. */
NodeID?: string;
/** The ID of the service this task is part of. */
ServiceID?: string;
/** The ID of container associated with this task. */
ContainerID?: string;
State?: TaskState;
DesiredState?: TaskState;
CreatedAt?: string;
UpdatedAt?: string;
}
export type ListSwarmTasksResponse = SwarmTaskListItem[];
export declare enum SwarmState {
/** Unknown case */
Unknown = "Unknown",

View File

@@ -1,6 +1,13 @@
import { useRead } from "@lib/hooks";
import { RequiredResourceComponents } from "@types";
import { Component } from "lucide-react";
import {
Component,
Diamond,
FolderCode,
KeyRound,
ListTodo,
Settings,
} from "lucide-react";
import { DeleteResource, NewResource, ResourcePageHeader } from "../common";
import { SwarmTable } from "./table";
import {
@@ -15,6 +22,7 @@ import { GroupActions } from "@components/group-actions";
import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/tooltip";
import { Card } from "@ui/card";
import { SwarmTabs } from "./tabs";
import { Link } from "react-router-dom";
export const useSwarm = (id?: string) =>
useRead("ListSwarms", {}, { refetchInterval: 10_000 }).data?.find(
@@ -124,3 +132,43 @@ export const SwarmComponents: RequiredResourceComponents = {
);
},
};
export type SwarmResourceType =
| "Node"
| "Service"
| "Task"
| "Secret"
| "Config";
export const SWARM_ICONS: {
[type in SwarmResourceType]: React.FC<{ size?: number }>;
} = {
Node: ({ size }) => <Diamond className={`w-${size} h-${size}`} />,
Service: ({ size }) => <FolderCode className={`w-${size} h-${size}`} />,
Task: ({ size }) => <ListTodo className={`w-${size} h-${size}`} />,
Secret: ({ size }) => <KeyRound className={`w-${size} h-${size}`} />,
Config: ({ size }) => <Settings className={`w-${size} h-${size}`} />,
};
export const SwarmLink = ({
type,
swarm_id,
resource_id,
name,
}: {
type: SwarmResourceType;
swarm_id: string;
resource_id: string | undefined;
name: string | undefined;
}) => {
const Icon = SWARM_ICONS[type];
return (
<Link
to={`/swarms/${swarm_id}/swarm-${type.toLowerCase()}/${resource_id}`}
className="flex gap-2 items-center hover:underline"
>
<Icon size={4} />
{name ?? "Unknown"}
</Link>
);
};

View File

@@ -2,10 +2,10 @@ import { Section } from "@components/layouts";
import { useRead } from "@lib/hooks";
import { DataTable, SortableHeader } from "@ui/data-table";
import { Dispatch, ReactNode, SetStateAction } from "react";
import { KeyRound, Search } from "lucide-react";
import { Search } from "lucide-react";
import { Input } from "@ui/input";
import { filterBySplit } from "@lib/utils";
import { Link } from "react-router-dom";
import { SwarmLink } from "..";
export const SwarmConfigs = ({
id,
@@ -55,15 +55,13 @@ export const SwarmConfigs = ({
<SortableHeader column={column} title="Name" />
),
cell: ({ row }) => (
<Link
to={`/swarms/${id}/swarm-config/${row.original.ID}`}
className="flex gap-2 items-center hover:underline"
>
<KeyRound className="w-4 h-4" />
{row.original.Name ?? "Unknown"}
</Link>
<SwarmLink
type="Config"
swarm_id={id}
resource_id={row.original.ID}
name={row.original.Name}
/>
),
size: 200,
},
{
accessorKey: "ID",
@@ -71,7 +69,18 @@ export const SwarmConfigs = ({
<SortableHeader column={column} title="Id" />
),
cell: ({ row }) => row.original.ID ?? "Unknown",
size: 200,
},
{
accessorKey: "UpdatedAt",
header: ({ column }) => (
<SortableHeader column={column} title="Updated" />
),
},
{
accessorKey: "CreatedAt",
header: ({ column }) => (
<SortableHeader column={column} title="Created" />
),
},
]}
/>

View File

@@ -2,10 +2,10 @@ import { Section } from "@components/layouts";
import { useRead } from "@lib/hooks";
import { DataTable, SortableHeader } from "@ui/data-table";
import { Dispatch, ReactNode, SetStateAction } from "react";
import { Diamond, Search } from "lucide-react";
import { Search } from "lucide-react";
import { Input } from "@ui/input";
import { filterBySplit } from "@lib/utils";
import { Link } from "react-router-dom";
import { SwarmLink } from "..";
export const SwarmNodes = ({
id,
@@ -55,13 +55,12 @@ export const SwarmNodes = ({
<SortableHeader column={column} title="Hostname" />
),
cell: ({ row }) => (
<Link
to={`/swarms/${id}/swarm-node/${row.original.ID}`}
className="flex gap-2 items-center hover:underline"
>
<Diamond className="w-4 h-4" />
{row.original.Hostname ?? "Unknown"}
</Link>
<SwarmLink
type="Node"
swarm_id={id}
resource_id={row.original.ID}
name={row.original.Hostname}
/>
),
size: 200,
},
@@ -79,7 +78,6 @@ export const SwarmNodes = ({
<SortableHeader column={column} title="Role" />
),
cell: ({ row }) => row.original.Role ?? "Unknown",
size: 200,
},
{
accessorKey: "Availability",
@@ -87,7 +85,6 @@ export const SwarmNodes = ({
<SortableHeader column={column} title="Availability" />
),
cell: ({ row }) => row.original.Availability ?? "Unknown",
size: 200,
},
{
accessorKey: "State",
@@ -96,6 +93,28 @@ export const SwarmNodes = ({
),
cell: ({ row }) => row.original.State ?? "Unknown",
},
{
accessorKey: "UpdatedAt",
header: ({ column }) => (
<SortableHeader column={column} title="Updated" />
),
cell: ({ row }) =>
row.original.UpdatedAt
? new Date(row.original.UpdatedAt).toLocaleString()
: "Unknown",
size: 200,
},
{
accessorKey: "CreatedAt",
header: ({ column }) => (
<SortableHeader column={column} title="Created" />
),
cell: ({ row }) =>
row.original.CreatedAt
? new Date(row.original.CreatedAt).toLocaleString()
: "Unknown",
size: 200,
},
]}
/>
</Section>

View File

@@ -2,10 +2,10 @@ import { Section } from "@components/layouts";
import { useRead } from "@lib/hooks";
import { DataTable, SortableHeader } from "@ui/data-table";
import { Dispatch, ReactNode, SetStateAction } from "react";
import { KeyRound, Search } from "lucide-react";
import { Search } from "lucide-react";
import { Input } from "@ui/input";
import { filterBySplit } from "@lib/utils";
import { Link } from "react-router-dom";
import { SwarmLink } from "..";
export const SwarmSecrets = ({
id,
@@ -24,7 +24,7 @@ export const SwarmSecrets = ({
const filtered = filterBySplit(
secrets,
search,
(secret) => secret.Spec?.Name ?? secret.ID ?? "Unknown"
(secret) => secret.Name ?? secret.ID ?? "Unknown"
);
return (
@@ -52,16 +52,15 @@ export const SwarmSecrets = ({
{
accessorKey: "Name",
header: ({ column }) => (
<SortableHeader column={column} title="ID" />
<SortableHeader column={column} title="Name" />
),
cell: ({ row }) => (
<Link
to={`/swarms/${id}/swarm-secret/${row.original.ID}`}
className="flex gap-2 items-center hover:underline"
>
<KeyRound className="w-4 h-4" />
{row.original.Spec?.Name ?? "Unknown"}
</Link>
<SwarmLink
type="Secret"
swarm_id={id}
resource_id={row.original.ID}
name={row.original.Name}
/>
),
size: 200,
},
@@ -73,6 +72,48 @@ export const SwarmSecrets = ({
cell: ({ row }) => row.original.ID ?? "Unknown",
size: 200,
},
{
accessorKey: "Driver",
header: ({ column }) => (
<SortableHeader column={column} title="Driver" />
),
cell: ({ row }) =>
row.original.Driver ?? (
<div className="text-muted-foreground">None</div>
),
},
{
accessorKey: "Templating",
header: ({ column }) => (
<SortableHeader column={column} title="Templating" />
),
cell: ({ row }) =>
row.original.Templating ?? (
<div className="text-muted-foreground">None</div>
),
},
{
accessorKey: "UpdatedAt",
header: ({ column }) => (
<SortableHeader column={column} title="Updated" />
),
cell: ({ row }) =>
row.original.UpdatedAt
? new Date(row.original.UpdatedAt).toLocaleString()
: "Unknown",
size: 200,
},
{
accessorKey: "CreatedAt",
header: ({ column }) => (
<SortableHeader column={column} title="Created" />
),
cell: ({ row }) =>
row.original.CreatedAt
? new Date(row.original.CreatedAt).toLocaleString()
: "Unknown",
size: 200,
},
]}
/>
</Section>

View File

@@ -2,10 +2,10 @@ import { Section } from "@components/layouts";
import { useRead } from "@lib/hooks";
import { DataTable, SortableHeader } from "@ui/data-table";
import { Dispatch, ReactNode, SetStateAction } from "react";
import { FolderCode, Search } from "lucide-react";
import { Search } from "lucide-react";
import { Input } from "@ui/input";
import { filterBySplit } from "@lib/utils";
import { Link } from "react-router-dom";
import { SwarmLink } from "..";
export const SwarmServices = ({
id,
@@ -55,13 +55,12 @@ export const SwarmServices = ({
<SortableHeader column={column} title="Name" />
),
cell: ({ row }) => (
<Link
to={`/swarms/${id}/swarm-service/${row.original.ID}`}
className="flex gap-2 items-center hover:underline"
>
<FolderCode className="w-4 h-4" />
{row.original.Name ?? "Unknown"}
</Link>
<SwarmLink
type="Service"
swarm_id={id}
resource_id={row.original.ID}
name={row.original.Name}
/>
),
size: 200,
},
@@ -73,6 +72,28 @@ export const SwarmServices = ({
cell: ({ row }) => row.original.ID ?? "Unknown",
size: 200,
},
{
accessorKey: "UpdatedAt",
header: ({ column }) => (
<SortableHeader column={column} title="Updated" />
),
cell: ({ row }) =>
row.original.UpdatedAt
? new Date(row.original.UpdatedAt).toLocaleString()
: "Unknown",
size: 200,
},
{
accessorKey: "CreatedAt",
header: ({ column }) => (
<SortableHeader column={column} title="Created" />
),
cell: ({ row }) =>
row.original.CreatedAt
? new Date(row.original.CreatedAt).toLocaleString()
: "Unknown",
size: 200,
},
]}
/>
</Section>

View File

@@ -2,10 +2,10 @@ import { Section } from "@components/layouts";
import { useRead } from "@lib/hooks";
import { DataTable, SortableHeader } from "@ui/data-table";
import { Dispatch, ReactNode, SetStateAction } from "react";
import { ListTodo, Search } from "lucide-react";
import { Search } from "lucide-react";
import { Input } from "@ui/input";
import { filterBySplit } from "@lib/utils";
import { Link } from "react-router-dom";
import { SwarmLink } from "..";
export const SwarmTasks = ({
id,
@@ -17,11 +17,25 @@ export const SwarmTasks = ({
_search: [string, Dispatch<SetStateAction<string>>];
}) => {
const [search, setSearch] = _search;
const tasks =
const services =
useRead("ListSwarmServices", { swarm: id }, { refetchInterval: 10_000 })
.data ?? [];
const _tasks =
useRead("ListSwarmTasks", { swarm: id }, { refetchInterval: 10_000 })
.data ?? [];
const filtered = filterBySplit(tasks, search, (task) => task.ID ?? "Unknown");
const tasks = _tasks.map((task) => {
return {
...task,
service: services.find((service) => task.ServiceID === service.ID),
};
});
const filtered = filterBySplit(
tasks,
search,
(task) => task.Name ?? task.service?.Name ?? "Unknown"
);
return (
<Section
@@ -51,16 +65,64 @@ export const SwarmTasks = ({
<SortableHeader column={column} title="Id" />
),
cell: ({ row }) => (
<Link
to={`/swarms/${id}/swarm-task/${row.original.ID}`}
className="flex gap-2 items-center hover:underline"
>
<ListTodo className="w-4 h-4" />
{row.original.ID ?? "Unknown"}
</Link>
<SwarmLink
type="Task"
swarm_id={id}
resource_id={row.original.ID}
name={row.original.ID}
/>
),
size: 200,
},
{
accessorKey: "service.Name",
header: ({ column }) => (
<SortableHeader column={column} title="Service" />
),
cell: ({ row }) => (
<SwarmLink
type="Service"
swarm_id={id}
resource_id={row.original.service?.ID}
name={row.original.service?.Name}
/>
),
size: 200,
},
{
accessorKey: "State",
header: ({ column }) => (
<SortableHeader column={column} title="State" />
),
},
{
accessorKey: "DesiredState",
header: ({ column }) => (
<SortableHeader column={column} title="Desired State" />
),
},
{
accessorKey: "UpdatedAt",
header: ({ column }) => (
<SortableHeader column={column} title="Updated" />
),
cell: ({ row }) =>
row.original.UpdatedAt
? new Date(row.original.UpdatedAt).toLocaleString()
: "Unknown",
size: 200,
},
{
accessorKey: "CreatedAt",
header: ({ column }) => (
<SortableHeader column={column} title="Created" />
),
cell: ({ row }) =>
row.original.CreatedAt
? new Date(row.original.CreatedAt).toLocaleString()
: "Unknown",
size: 200,
},
]}
/>
</Section>

View File

@@ -632,7 +632,11 @@ export const ShowHideButton = ({
);
};
type DockerResourceType = "container" | "network" | "image" | "volume";
type DockerResourceType =
| "container"
| "network"
| "image"
| "volume";
export const DOCKER_LINK_ICONS: {
[type in DockerResourceType]: React.FC<{