mirror of
https://github.com/henrygd/beszel.git
synced 2026-09-21 08:57:48 +02:00
feat(agent): Add docker image update available flag (#2211)
Co-authored-by: henrygd <hank@henrygd.me>
This commit is contained in:
@@ -186,11 +186,12 @@ type Stats struct {
|
||||
NetworkRecv float64 `json:"nr,omitzero" cbor:"4,keyasint,omitzero"` // deprecated 0.18.3 (MB) - keep field for old agents/records
|
||||
Bandwidth [2]uint64 `json:"b,omitzero" cbor:"9,keyasint,omitzero"` // [sent bytes, recv bytes]
|
||||
|
||||
Health DockerHealth `json:"-" cbor:"5,keyasint"`
|
||||
Status string `json:"-" cbor:"6,keyasint"`
|
||||
Id string `json:"-" cbor:"7,keyasint"`
|
||||
Image string `json:"-" cbor:"8,keyasint"`
|
||||
Ports string `json:"-" cbor:"10,keyasint"`
|
||||
Health DockerHealth `json:"-" cbor:"5,keyasint"`
|
||||
Status string `json:"-" cbor:"6,keyasint"`
|
||||
Id string `json:"-" cbor:"7,keyasint"`
|
||||
Image string `json:"-" cbor:"8,keyasint"`
|
||||
Ports string `json:"-" cbor:"10,keyasint"`
|
||||
UpdateAvailable bool `json:"u,omitzero" cbor:"11,keyasint,omitzero"`
|
||||
// PrevCpu [2]uint64 `json:"-"`
|
||||
CpuSystem uint64 `json:"-"`
|
||||
CpuContainer uint64 `json:"-"`
|
||||
|
||||
46
internal/hub/systems/container_records_test.go
Normal file
46
internal/hub/systems/container_records_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
//go:build testing
|
||||
|
||||
package systems
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/henrygd/beszel/internal/entities/container"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateContainerRecordsPersistsImageUpdateAvailability(t *testing.T) {
|
||||
_, app := newTestSystemWithHub(t)
|
||||
|
||||
const (
|
||||
systemID = "system123"
|
||||
containerID = "abcdef123456"
|
||||
image = "nginx:latest"
|
||||
)
|
||||
|
||||
data := &container.Stats{
|
||||
Id: containerID,
|
||||
Name: "web",
|
||||
Image: image,
|
||||
UpdateAvailable: true,
|
||||
}
|
||||
require.NoError(t, createContainerRecords(app, []*container.Stats{data}, systemID))
|
||||
|
||||
var record struct {
|
||||
Image string `db:"image"`
|
||||
UpdateAvailable bool `db:"updatable"`
|
||||
}
|
||||
require.NoError(t, app.DB().Select("image", "updatable").From("containers").
|
||||
Where(dbx.HashExp{"id": containerID}).One(&record))
|
||||
assert.Equal(t, image, record.Image)
|
||||
assert.True(t, record.UpdateAvailable)
|
||||
|
||||
data.UpdateAvailable = false
|
||||
require.NoError(t, createContainerRecords(app, []*container.Stats{data}, systemID))
|
||||
require.NoError(t, app.DB().Select("image", "updatable").From("containers").
|
||||
Where(dbx.HashExp{"id": containerID}).One(&record))
|
||||
assert.Equal(t, image, record.Image)
|
||||
assert.False(t, record.UpdateAvailable)
|
||||
}
|
||||
@@ -376,7 +376,7 @@ func createContainerRecords(app core.App, data []*container.Stats, systemId stri
|
||||
valueStrings := make([]string, 0, len(data))
|
||||
for i, container := range data {
|
||||
suffix := fmt.Sprintf("%d", i)
|
||||
valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:image%[1]s}, {:ports%[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}, {:ports%[1]s}, {:status%[1]s}, {:health%[1]s}, {:cpu%[1]s}, {:memory%[1]s}, {:net%[1]s}, {:updateAvailable%[1]s}, {:updated})", suffix))
|
||||
params["id"+suffix] = container.Id
|
||||
params["name"+suffix] = container.Name
|
||||
params["image"+suffix] = container.Image
|
||||
@@ -390,9 +390,10 @@ func createContainerRecords(app core.App, data []*container.Stats, systemId stri
|
||||
netBytes = uint64((container.NetworkSent + container.NetworkRecv) * 1024 * 1024)
|
||||
}
|
||||
params["net"+suffix] = netBytes
|
||||
params["updateAvailable"+suffix] = container.UpdateAvailable
|
||||
}
|
||||
queryString := fmt.Sprintf(
|
||||
"INSERT INTO containers (id, system, name, image, ports, status, health, cpu, memory, net, updated) VALUES %s ON CONFLICT(id) DO UPDATE SET system = excluded.system, name = excluded.name, image = excluded.image, ports = excluded.ports, 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, ports, status, health, cpu, memory, net, updatable, updated) VALUES %s ON CONFLICT(id) DO UPDATE SET system = excluded.system, name = excluded.name, image = excluded.image, ports = excluded.ports, status = excluded.status, health = excluded.health, cpu = excluded.cpu, memory = excluded.memory, net = excluded.net, updatable = excluded.updatable, updated = excluded.updated",
|
||||
strings.Join(valueStrings, ","),
|
||||
)
|
||||
_, err := app.DB().NewQuery(queryString).Bind(params).Execute()
|
||||
|
||||
24
internal/migrations/1789079746_container_update_available.go
Normal file
24
internal/migrations/1789079746_container_update_available.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("containers")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
collection.Fields.Add(&core.BoolField{Name: "updatable"})
|
||||
return app.Save(collection)
|
||||
}, func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("containers")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
collection.Fields.RemoveByName("updatable")
|
||||
return app.Save(collection)
|
||||
})
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { cn, decimalString, formatBytes, hourWithSeconds } from "@/lib/utils"
|
||||
import type { ContainerRecord } from "@/types"
|
||||
import { ContainerHealth, ContainerHealthLabels } from "@/lib/enums"
|
||||
import {
|
||||
CircleArrowUpIcon,
|
||||
ClockIcon,
|
||||
ContainerIcon,
|
||||
CpuIcon,
|
||||
@@ -177,11 +178,25 @@ export const containerChartCols: ColumnDef<ContainerRecord>[] = [
|
||||
header: ({ column }) => (
|
||||
<HeaderButton column={column} name={t({ message: "Image", context: "Docker image" })} Icon={LayersIcon} />
|
||||
),
|
||||
cell: ({ getValue }) => {
|
||||
cell: ({ getValue, row }) => {
|
||||
const val = getValue() as string
|
||||
return (
|
||||
<div className="ms-1 xl:w-40 truncate" title={val}>
|
||||
{val}
|
||||
<div className="ms-1 xl:w-40 flex items-center gap-2">
|
||||
<span className="truncate" title={val}>
|
||||
{val}
|
||||
</span>
|
||||
{row.original.updatable && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
className="shrink-0 rounded-sm text-emerald-600 dark:text-emerald-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
aria-label={t`Image update available`}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<CircleArrowUpIcon className="size-4" aria-hidden="true" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t`Image update available`}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
|
||||
function fetchData(systemId?: string) {
|
||||
pb.collection<ContainerRecord>("containers")
|
||||
.getList(0, 2000, {
|
||||
fields: "id,name,image,ports,cpu,memory,net,health,status,system,updated",
|
||||
fields: "id,name,image,updatable,ports,cpu,memory,net,health,status,system,updated",
|
||||
filter: systemId ? pb.filter("system={:system}", { system: systemId }) : undefined,
|
||||
})
|
||||
.then(({ items }) => {
|
||||
|
||||
1
internal/site/src/types.d.ts
vendored
1
internal/site/src/types.d.ts
vendored
@@ -335,6 +335,7 @@ export interface ContainerRecord extends RecordModel {
|
||||
system: string
|
||||
name: string
|
||||
image: string
|
||||
updatable?: boolean
|
||||
ports: string
|
||||
cpu: number
|
||||
memory: number
|
||||
|
||||
Reference in New Issue
Block a user