mirror of
https://github.com/henrygd/beszel.git
synced 2025-12-17 02:36:17 +01:00
add image name to containers table (#1302)
This commit is contained in:
@@ -356,7 +356,7 @@ func (dm *dockerManager) updateContainerStats(ctr *container.ApiInfo, cacheTimeM
|
|||||||
// add empty values if they doesn't exist in map
|
// add empty values if they doesn't exist in map
|
||||||
stats, initialized := dm.containerStatsMap[ctr.IdShort]
|
stats, initialized := dm.containerStatsMap[ctr.IdShort]
|
||||||
if !initialized {
|
if !initialized {
|
||||||
stats = &container.Stats{Name: name, Id: ctr.IdShort}
|
stats = &container.Stats{Name: name, Id: ctr.IdShort, Image: ctr.Image}
|
||||||
dm.containerStatsMap[ctr.IdShort] = stats
|
dm.containerStatsMap[ctr.IdShort] = stats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ type ApiInfo struct {
|
|||||||
Names []string
|
Names []string
|
||||||
Status string
|
Status string
|
||||||
State string
|
State string
|
||||||
// Image string
|
Image string
|
||||||
// ImageID string
|
// ImageID string
|
||||||
// Command string
|
// Command string
|
||||||
// Created int64
|
// Created int64
|
||||||
@@ -130,6 +130,7 @@ type Stats struct {
|
|||||||
Health DockerHealth `json:"-" cbor:"5,keyasint"`
|
Health DockerHealth `json:"-" cbor:"5,keyasint"`
|
||||||
Status string `json:"-" cbor:"6,keyasint"`
|
Status string `json:"-" cbor:"6,keyasint"`
|
||||||
Id string `json:"-" cbor:"7,keyasint"`
|
Id string `json:"-" cbor:"7,keyasint"`
|
||||||
|
Image string `json:"-" cbor:"8,keyasint"`
|
||||||
// PrevCpu [2]uint64 `json:"-"`
|
// PrevCpu [2]uint64 `json:"-"`
|
||||||
CpuSystem uint64 `json:"-"`
|
CpuSystem uint64 `json:"-"`
|
||||||
CpuContainer uint64 `json:"-"`
|
CpuContainer uint64 `json:"-"`
|
||||||
|
|||||||
@@ -196,9 +196,10 @@ func createContainerRecords(app core.App, data []*container.Stats, systemId stri
|
|||||||
valueStrings := make([]string, 0, len(data))
|
valueStrings := make([]string, 0, len(data))
|
||||||
for i, container := range data {
|
for i, container := range data {
|
||||||
suffix := fmt.Sprintf("%d", i)
|
suffix := fmt.Sprintf("%d", i)
|
||||||
valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:status%[1]s}, {:health%[1]s}, {:cpu%[1]s}, {:memory%[1]s}, {:net%[1]s}, {:updated})", suffix))
|
valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:image%[1]s}, {:status%[1]s}, {:health%[1]s}, {:cpu%[1]s}, {:memory%[1]s}, {:net%[1]s}, {:updated})", suffix))
|
||||||
params["id"+suffix] = container.Id
|
params["id"+suffix] = container.Id
|
||||||
params["name"+suffix] = container.Name
|
params["name"+suffix] = container.Name
|
||||||
|
params["image"+suffix] = container.Image
|
||||||
params["status"+suffix] = container.Status
|
params["status"+suffix] = container.Status
|
||||||
params["health"+suffix] = container.Health
|
params["health"+suffix] = container.Health
|
||||||
params["cpu"+suffix] = container.Cpu
|
params["cpu"+suffix] = container.Cpu
|
||||||
@@ -206,7 +207,7 @@ func createContainerRecords(app core.App, data []*container.Stats, systemId stri
|
|||||||
params["net"+suffix] = container.NetworkSent + container.NetworkRecv
|
params["net"+suffix] = container.NetworkSent + container.NetworkRecv
|
||||||
}
|
}
|
||||||
queryString := fmt.Sprintf(
|
queryString := fmt.Sprintf(
|
||||||
"INSERT INTO containers (id, system, name, status, health, cpu, memory, net, updated) VALUES %s ON CONFLICT(id) DO UPDATE SET system = excluded.system, name = excluded.name, status = excluded.status, health = excluded.health, cpu = excluded.cpu, memory = excluded.memory, net = excluded.net, updated = excluded.updated",
|
"INSERT INTO containers (id, system, name, image, status, health, cpu, memory, net, updated) VALUES %s ON CONFLICT(id) DO UPDATE SET system = excluded.system, name = excluded.name, image = excluded.image, status = excluded.status, health = excluded.health, cpu = excluded.cpu, memory = excluded.memory, net = excluded.net, updated = excluded.updated",
|
||||||
strings.Join(valueStrings, ","),
|
strings.Join(valueStrings, ","),
|
||||||
)
|
)
|
||||||
_, err := app.DB().NewQuery(queryString).Bind(params).Execute()
|
_, err := app.DB().NewQuery(queryString).Bind(params).Execute()
|
||||||
|
|||||||
@@ -984,6 +984,20 @@ func init() {
|
|||||||
"required": true,
|
"required": true,
|
||||||
"system": false,
|
"system": false,
|
||||||
"type": "number"
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text3309110367",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "image",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"indexes": [
|
"indexes": [
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
ClockIcon,
|
ClockIcon,
|
||||||
ContainerIcon,
|
ContainerIcon,
|
||||||
CpuIcon,
|
CpuIcon,
|
||||||
HashIcon,
|
LayersIcon,
|
||||||
MemoryStickIcon,
|
MemoryStickIcon,
|
||||||
ServerIcon,
|
ServerIcon,
|
||||||
ShieldCheckIcon,
|
ShieldCheckIcon,
|
||||||
@@ -58,15 +58,15 @@ export const containerChartCols: ColumnDef<ContainerRecord>[] = [
|
|||||||
return <span className="ms-1.5 xl:w-32 block truncate">{allSystems[getValue() as string]?.name ?? ""}</span>
|
return <span className="ms-1.5 xl:w-32 block truncate">{allSystems[getValue() as string]?.name ?? ""}</span>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
id: "id",
|
// id: "id",
|
||||||
accessorFn: (record) => record.id,
|
// accessorFn: (record) => record.id,
|
||||||
sortingFn: (a, b) => a.original.id.localeCompare(b.original.id),
|
// sortingFn: (a, b) => a.original.id.localeCompare(b.original.id),
|
||||||
header: ({ column }) => <HeaderButton column={column} name="ID" Icon={HashIcon} />,
|
// header: ({ column }) => <HeaderButton column={column} name="ID" Icon={HashIcon} />,
|
||||||
cell: ({ getValue }) => {
|
// cell: ({ getValue }) => {
|
||||||
return <span className="ms-1.5 me-3 font-mono">{getValue() as string}</span>
|
// return <span className="ms-1.5 me-3 font-mono">{getValue() as string}</span>
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
id: "cpu",
|
id: "cpu",
|
||||||
accessorFn: (record) => record.cpu,
|
accessorFn: (record) => record.cpu,
|
||||||
@@ -125,6 +125,15 @@ export const containerChartCols: ColumnDef<ContainerRecord>[] = [
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "image",
|
||||||
|
sortingFn: (a, b) => a.original.image.localeCompare(b.original.image),
|
||||||
|
accessorFn: (record) => record.image,
|
||||||
|
header: ({ column }) => <HeaderButton column={column} name={t({ message: "Image", context: "Docker image" })} Icon={LayersIcon} />,
|
||||||
|
cell: ({ getValue }) => {
|
||||||
|
return <span className="ms-1.5 xl:w-36 block truncate">{getValue() as string}</span>
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "status",
|
id: "status",
|
||||||
accessorFn: (record) => record.status,
|
accessorFn: (record) => record.status,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pbOptions = {
|
const pbOptions = {
|
||||||
fields: "id,name,cpu,memory,net,health,status,system,updated",
|
fields: "id,name,image,cpu,memory,net,health,status,system,updated",
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchData = (lastXMs: number) => {
|
const fetchData = (lastXMs: number) => {
|
||||||
@@ -123,7 +123,8 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
|
|||||||
const name = container.name ?? ""
|
const name = container.name ?? ""
|
||||||
const status = container.status ?? ""
|
const status = container.status ?? ""
|
||||||
const healthLabel = ContainerHealthLabels[container.health as ContainerHealth] ?? ""
|
const healthLabel = ContainerHealthLabels[container.health as ContainerHealth] ?? ""
|
||||||
const searchString = `${systemName} ${id} ${name} ${healthLabel} ${status}`.toLowerCase()
|
const image = container.image ?? ""
|
||||||
|
const searchString = `${systemName} ${id} ${name} ${healthLabel} ${status} ${image}`.toLowerCase()
|
||||||
|
|
||||||
return (filterValue as string)
|
return (filterValue as string)
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@@ -195,7 +196,7 @@ const AllContainersTable = memo(
|
|||||||
>
|
>
|
||||||
{/* add header height to table size */}
|
{/* add header height to table size */}
|
||||||
<div style={{ height: `${virtualizer.getTotalSize() + 48}px`, paddingTop, paddingBottom }}>
|
<div style={{ height: `${virtualizer.getTotalSize() + 48}px`, paddingTop, paddingBottom }}>
|
||||||
<table className="text-sm w-full h-full">
|
<table className="text-sm w-full h-full text-nowrap">
|
||||||
<ContainersTableHead table={table} />
|
<ContainersTableHead table={table} />
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{rows.length ? (
|
{rows.length ? (
|
||||||
@@ -325,11 +326,13 @@ function ContainerSheet({ sheetOpen, setSheetOpen, activeContainer }: { sheetOpe
|
|||||||
<SheetContent className="w-full sm:max-w-220 p-2">
|
<SheetContent className="w-full sm:max-w-220 p-2">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>{container.name}</SheetTitle>
|
<SheetTitle>{container.name}</SheetTitle>
|
||||||
<SheetDescription className="flex items-center gap-2">
|
<SheetDescription className="flex flex-wrap items-center gap-x-2 gap-y-1">
|
||||||
<Link className="hover:underline" href={getPagePath($router, "system", { id: container.system })}>{$allSystemsById.get()[container.system]?.name ?? ""}</Link>
|
<Link className="hover:underline" href={getPagePath($router, "system", { id: container.system })}>{$allSystemsById.get()[container.system]?.name ?? ""}</Link>
|
||||||
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
|
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
|
||||||
{container.status}
|
{container.status}
|
||||||
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
|
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
|
||||||
|
{container.image}
|
||||||
|
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
|
||||||
{container.id}
|
{container.id}
|
||||||
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
|
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
|
||||||
{ContainerHealthLabels[container.health as ContainerHealth]}
|
{ContainerHealthLabels[container.health as ContainerHealth]}
|
||||||
|
|||||||
1
internal/site/src/types.d.ts
vendored
1
internal/site/src/types.d.ts
vendored
@@ -240,6 +240,7 @@ export interface ContainerRecord extends RecordModel {
|
|||||||
id: string
|
id: string
|
||||||
system: string
|
system: string
|
||||||
name: string
|
name: string
|
||||||
|
image: string
|
||||||
cpu: number
|
cpu: number
|
||||||
memory: number
|
memory: number
|
||||||
net: number
|
net: number
|
||||||
|
|||||||
@@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
- Add `MFA_OTP` environment variable to enable email-based one-time password for users and/or superusers.
|
- Add `MFA_OTP` environment variable to enable email-based one-time password for users and/or superusers.
|
||||||
|
|
||||||
|
- Add image name to containers table. (#1302)
|
||||||
|
|
||||||
- Add spacing for long temperature chart tooltip. (#1299)
|
- Add spacing for long temperature chart tooltip. (#1299)
|
||||||
|
|
||||||
|
- Fix sorting by status in containers table. (#1294)
|
||||||
|
|
||||||
## 0.14.0
|
## 0.14.0
|
||||||
|
|
||||||
- Add `/containers` page for viewing current status of all running containers. (#928)
|
- Add `/containers` page for viewing current status of all running containers. (#928)
|
||||||
|
|||||||
Reference in New Issue
Block a user