Compare commits

...

5 Commits

Author SHA1 Message Date
Nathan Heaps
d608cf0955 fix: send battery stats even if some batteries are missing stats (#1287)
Implement battery error reporting mechanism

Added error reporting for battery information retrieval.

Handles cases like bluetooth devices where `battery` finds it but it has incomplete information about that battery, which would have otherwise caused a null pointer error.

Related: #1254
2025-10-25 13:23:29 -04:00
henrygd
b9139a1f9b strip env vars from container detail (#1305) 2025-10-25 12:58:13 -04:00
henrygd
7f372c46db add alpine image + add smartmontools to intel / nvidia images 2025-10-25 11:59:57 -04:00
henrygd
40010ad9b9 small frontend refactoring / style updates 2025-10-25 11:58:28 -04:00
henrygd
5927f45a4a install-agent.sh: add 'beszel' user to disk group for device access 2025-10-25 11:56:26 -04:00
14 changed files with 186 additions and 63 deletions

View File

@@ -12,65 +12,137 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
# henrygd/beszel
- image: henrygd/beszel - image: henrygd/beszel
context: ./
dockerfile: ./internal/dockerfile_hub dockerfile: ./internal/dockerfile_hub
registry: docker.io registry: docker.io
username_secret: DOCKERHUB_USERNAME username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN password_secret: DOCKERHUB_TOKEN
tags: |
type=raw,value=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }}
# henrygd/beszel-agent
- image: henrygd/beszel-agent - image: henrygd/beszel-agent
context: ./
dockerfile: ./internal/dockerfile_agent dockerfile: ./internal/dockerfile_agent
registry: docker.io registry: docker.io
username_secret: DOCKERHUB_USERNAME username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN password_secret: DOCKERHUB_TOKEN
tags: |
type=raw,value=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }}
# henrygd/beszel-agent-nvidia
- image: henrygd/beszel-agent-nvidia - image: henrygd/beszel-agent-nvidia
context: ./
dockerfile: ./internal/dockerfile_agent_nvidia dockerfile: ./internal/dockerfile_agent_nvidia
platforms: linux/amd64 platforms: linux/amd64
registry: docker.io registry: docker.io
username_secret: DOCKERHUB_USERNAME username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN password_secret: DOCKERHUB_TOKEN
tags: |
type=raw,value=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }}
# henrygd/beszel-agent-intel
- image: henrygd/beszel-agent-intel - image: henrygd/beszel-agent-intel
context: ./
dockerfile: ./internal/dockerfile_agent_intel dockerfile: ./internal/dockerfile_agent_intel
platforms: linux/amd64 platforms: linux/amd64
registry: docker.io registry: docker.io
username_secret: DOCKERHUB_USERNAME username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN password_secret: DOCKERHUB_TOKEN
tags: |
type=raw,value=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }}
# henrygd/beszel-agent:alpine
- image: henrygd/beszel-agent
dockerfile: ./internal/dockerfile_agent_alpine
registry: docker.io
username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN
tags: |
type=raw,value=alpine
type=semver,pattern={{version}}-alpine
type=semver,pattern={{major}}.{{minor}}-alpine
type=semver,pattern={{major}}-alpine
# ghcr.io/henrygd/beszel
- image: ghcr.io/${{ github.repository }}/beszel - image: ghcr.io/${{ github.repository }}/beszel
context: ./
dockerfile: ./internal/dockerfile_hub dockerfile: ./internal/dockerfile_hub
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password_secret: GITHUB_TOKEN password_secret: GITHUB_TOKEN
tags: |
type=raw,value=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }}
# ghcr.io/henrygd/beszel-agent
- image: ghcr.io/${{ github.repository }}/beszel-agent - image: ghcr.io/${{ github.repository }}/beszel-agent
context: ./
dockerfile: ./internal/dockerfile_agent dockerfile: ./internal/dockerfile_agent
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password_secret: GITHUB_TOKEN password_secret: GITHUB_TOKEN
tags: |
type=raw,value=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }}
# ghcr.io/henrygd/beszel-agent-nvidia
- image: ghcr.io/${{ github.repository }}/beszel-agent-nvidia - image: ghcr.io/${{ github.repository }}/beszel-agent-nvidia
context: ./
dockerfile: ./internal/dockerfile_agent_nvidia dockerfile: ./internal/dockerfile_agent_nvidia
platforms: linux/amd64 platforms: linux/amd64
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password_secret: GITHUB_TOKEN password_secret: GITHUB_TOKEN
tags: |
type=raw,value=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }}
# ghcr.io/henrygd/beszel-agent-intel
- image: ghcr.io/${{ github.repository }}/beszel-agent-intel - image: ghcr.io/${{ github.repository }}/beszel-agent-intel
context: ./
dockerfile: ./internal/dockerfile_agent_intel dockerfile: ./internal/dockerfile_agent_intel
platforms: linux/amd64 platforms: linux/amd64
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password_secret: GITHUB_TOKEN password_secret: GITHUB_TOKEN
tags: |
type=raw,value=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }}
# ghcr.io/henrygd/beszel-agent:alpine
- image: ghcr.io/${{ github.repository }}/beszel-agent
dockerfile: ./internal/dockerfile_agent_alpine
registry: ghcr.io
username: ${{ github.actor }}
password_secret: GITHUB_TOKEN
tags: |
type=raw,value=alpine
type=semver,pattern={{version}}-alpine
type=semver,pattern={{major}}.{{minor}}-alpine
type=semver,pattern={{major}}-alpine
permissions: permissions:
contents: read contents: read
@@ -100,12 +172,7 @@ jobs:
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
images: ${{ matrix.image }} images: ${{ matrix.image }}
tags: | tags: ${{ matrix.tags }}
type=raw,value=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }}
# https://github.com/docker/login-action # https://github.com/docker/login-action
- name: Login to Docker Hub - name: Login to Docker Hub
@@ -123,7 +190,7 @@ jobs:
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: "${{ matrix.context }}" context: ./
file: ${{ matrix.dockerfile }} file: ${{ matrix.dockerfile }}
platforms: ${{ matrix.platforms || 'linux/amd64,linux/arm64,linux/arm/v7' }} platforms: ${{ matrix.platforms || 'linux/amd64,linux/arm64,linux/arm/v7' }}
push: ${{ github.ref_type == 'tag' && secrets[matrix.password_secret] != '' }} push: ${{ github.ref_type == 'tag' && secrets[matrix.password_secret] != '' }}

View File

@@ -5,6 +5,8 @@ package battery
import ( import (
"errors" "errors"
"fmt"
"os"
"log/slog" "log/slog"
"github.com/distatus/battery" "github.com/distatus/battery"
@@ -19,33 +21,60 @@ func HasReadableBattery() bool {
return systemHasBattery return systemHasBattery
} }
haveCheckedBattery = true haveCheckedBattery = true
bat, err := battery.Get(0) batteries,err := battery.GetAll()
systemHasBattery = err == nil && bat != nil && bat.Design != 0 && bat.Full != 0 if err != nil {
// even if there's errors getting some batteries, the system
// definitely has a battery if the list is not empty.
// This list will include everything `battery` can find,
// including things like bluetooth devices.
fmt.Fprintln(os.Stderr, err)
}
systemHasBattery = len(batteries) > 0
if !systemHasBattery { if !systemHasBattery {
slog.Debug("No battery found", "err", err) slog.Debug("No battery found", "err", err)
} }
return systemHasBattery return systemHasBattery
} }
// GetBatteryStats returns the current battery percent and charge state // GetBatteryStats returns the current battery percent and charge state
// percent = (current charge of all batteries) / (sum of designed/full capacity of all batteries)
func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) { func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
if !systemHasBattery { if !HasReadableBattery() {
return batteryPercent, batteryState, errors.ErrUnsupported return batteryPercent, batteryState, errors.ErrUnsupported
} }
batteries, err := battery.GetAll() batteries, err := battery.GetAll()
if err != nil || len(batteries) == 0 { // we'll handle errors later by skipping batteries with errors, rather
return batteryPercent, batteryState, err // than skipping everything because of the presence of some errors.
if len(batteries) == 0 {
return batteryPercent, batteryState, errors.New("no batteries")
} }
totalCapacity := float64(0) totalCapacity := float64(0)
totalCharge := float64(0) totalCharge := float64(0)
for _, bat := range batteries { errs, partialErrs := err.(battery.Errors)
if bat.Design != 0 {
totalCapacity += bat.Design for i, bat := range batteries {
} else { if partialErrs && errs[i] != nil {
totalCapacity += bat.Full // if there were some errors, like missing data, skip it
continue
} }
if bat.Full == 0 {
// skip batteries with no capacity. Charge is unlikely to ever be zero, but
// we can't guarantee that, so don't skip based on charge.
continue
}
totalCapacity += bat.Full
totalCharge += bat.Current totalCharge += bat.Current
} }
if totalCapacity == 0 {
// for macs there's sometimes a ghost battery with 0 capacity
// https://github.com/distatus/battery/issues/34
// Instead of skipping over those batteries, we'll check for total 0 capacity
// and return an error. This also prevents a divide by zero.
return batteryPercent, batteryState, errors.New("no battery capacity")
}
batteryPercent = uint8(totalCharge / totalCapacity * 100) batteryPercent = uint8(totalCharge / totalCapacity * 100)
batteryState = uint8(batteries[0].State.Raw) batteryState = uint8(batteries[0].State.Raw)
return batteryPercent, batteryState, nil return batteryPercent, batteryState, nil

View File

@@ -596,30 +596,34 @@ func getDockerHost() string {
} }
// getContainerInfo fetches the inspection data for a container // getContainerInfo fetches the inspection data for a container
func (dm *dockerManager) getContainerInfo(ctx context.Context, containerID string) (string, error) { func (dm *dockerManager) getContainerInfo(ctx context.Context, containerID string) ([]byte, error) {
endpoint := fmt.Sprintf("http://localhost/containers/%s/json", containerID) endpoint := fmt.Sprintf("http://localhost/containers/%s/json", containerID)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil { if err != nil {
return "", err return nil, err
} }
resp, err := dm.client.Do(req) resp, err := dm.client.Do(req)
if err != nil { if err != nil {
return "", err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024)) body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
return "", fmt.Errorf("container info request failed: %s: %s", resp.Status, strings.TrimSpace(string(body))) return nil, fmt.Errorf("container info request failed: %s: %s", resp.Status, strings.TrimSpace(string(body)))
} }
data, err := io.ReadAll(resp.Body) // Remove sensitive environment variables from Config.Env
if err != nil { var containerInfo map[string]any
return "", err if err := json.NewDecoder(resp.Body).Decode(&containerInfo); err != nil {
return nil, err
}
if config, ok := containerInfo["Config"].(map[string]any); ok {
delete(config, "Env")
} }
return string(data), nil return json.Marshal(containerInfo)
} }
// getLogs fetches the logs for a container // getLogs fetches the logs for a container

View File

@@ -154,7 +154,7 @@ func (h *GetContainerInfoHandler) Handle(hctx *HandlerContext) error {
return err return err
} }
return hctx.SendResponse(info, hctx.RequestID) return hctx.SendResponse(string(info), hctx.RequestID)
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////

View File

@@ -78,8 +78,9 @@ func (a *Agent) getSystemStats(cacheTimeMs uint16) system.Stats {
var systemStats system.Stats var systemStats system.Stats
// battery // battery
if battery.HasReadableBattery() { if batteryPercent, batteryState, err := battery.GetBatteryStats(); err == nil {
systemStats.Battery[0], systemStats.Battery[1], _ = battery.GetBatteryStats() systemStats.Battery[0] = batteryPercent
systemStats.Battery[1] = batteryState
} }
// cpu percent // cpu percent

14
go.sum
View File

@@ -169,20 +169,18 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q= modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=

View File

@@ -0,0 +1,28 @@
FROM --platform=$BUILDPLATFORM golang:alpine AS builder
WORKDIR /app
COPY ../go.mod ../go.sum ./
RUN go mod download
# Copy source files
COPY . ./
# Build
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./internal/cmd/agent
RUN rm -rf /tmp/*
# --------------------------
# Final image: default scratch-based agent
# --------------------------
FROM alpine:latest
COPY --from=builder /agent /agent
RUN apk add --no-cache smartmontools
# Ensure data persistence across container recreations
VOLUME ["/var/lib/beszel-agent"]
ENTRYPOINT ["/agent"]

View File

@@ -20,7 +20,7 @@ FROM alpine:edge
COPY --from=builder /agent /agent COPY --from=builder /agent /agent
RUN apk add --no-cache -X https://dl-cdn.alpinelinux.org/alpine/edge/testing igt-gpu-tools RUN apk add --no-cache -X https://dl-cdn.alpinelinux.org/alpine/edge/testing igt-gpu-tools smartmontools
# Ensure data persistence across container recreations # Ensure data persistence across container recreations
VOLUME ["/var/lib/beszel-agent"] VOLUME ["/var/lib/beszel-agent"]

View File

@@ -24,6 +24,8 @@ COPY --from=builder /agent /agent
# this is so we don't need to create the /tmp directory in the scratch container # this is so we don't need to create the /tmp directory in the scratch container
COPY --from=builder /tmp /tmp COPY --from=builder /tmp /tmp
RUN apt-get update && apt-get install -y smartmontools && rm -rf /var/lib/apt/lists/*
# Ensure data persistence across container recreations # Ensure data persistence across container recreations
VOLUME ["/var/lib/beszel-agent"] VOLUME ["/var/lib/beszel-agent"]

View File

@@ -40,7 +40,7 @@ export const containerChartCols: ColumnDef<ContainerRecord>[] = [
accessorFn: (record) => record.name, accessorFn: (record) => record.name,
header: ({ column }) => <HeaderButton column={column} name={t`Name`} Icon={ContainerIcon} />, header: ({ column }) => <HeaderButton column={column} name={t`Name`} Icon={ContainerIcon} />,
cell: ({ getValue }) => { cell: ({ getValue }) => {
return <span className="ms-1.5 xl:w-45 block truncate">{getValue() as string}</span> return <span className="ms-1.5 xl:w-48 block truncate">{getValue() as string}</span>
}, },
}, },
{ {
@@ -55,7 +55,7 @@ export const containerChartCols: ColumnDef<ContainerRecord>[] = [
header: ({ column }) => <HeaderButton column={column} name={t`System`} Icon={ServerIcon} />, header: ({ column }) => <HeaderButton column={column} name={t`System`} Icon={ServerIcon} />,
cell: ({ getValue }) => { cell: ({ getValue }) => {
const allSystems = useStore($allSystemsById) const allSystems = useStore($allSystemsById)
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-34 block truncate">{allSystems[getValue() as string]?.name ?? ""}</span>
}, },
}, },
// { // {
@@ -131,7 +131,7 @@ export const containerChartCols: ColumnDef<ContainerRecord>[] = [
accessorFn: (record) => record.image, accessorFn: (record) => record.image,
header: ({ column }) => <HeaderButton column={column} name={t({ message: "Image", context: "Docker image" })} Icon={LayersIcon} />, header: ({ column }) => <HeaderButton column={column} name={t({ message: "Image", context: "Docker image" })} Icon={LayersIcon} />,
cell: ({ getValue }) => { cell: ({ getValue }) => {
return <span className="ms-1.5 xl:w-36 block truncate">{getValue() as string}</span> return <span className="ms-1.5 xl:w-40 block truncate">{getValue() as string}</span>
}, },
}, },
{ {

View File

@@ -63,7 +63,7 @@ export default function ConfigYaml() {
</Trans> </Trans>
</p> </p>
<Alert className="my-4 border-destructive text-destructive w-auto table md:pe-6"> <Alert className="my-4 border-destructive text-destructive w-auto table md:pe-6">
<AlertCircleIcon className="h-4 w-4 stroke-destructive" /> <AlertCircleIcon className="size-4.5 stroke-destructive" />
<AlertTitle> <AlertTitle>
<Trans>Caution - potential data loss</Trans> <Trans>Caution - potential data loss</Trans>
</AlertTitle> </AlertTitle>

View File

@@ -36,10 +36,6 @@ export const smartColumns: ColumnDef<SmartAttribute>[] = [
{ {
accessorKey: "id", accessorKey: "id",
header: "ID", header: "ID",
cell: ({ getValue }) => {
const id = getValue() as number | undefined
return <div className="font-mono text-sm">{id?.toString() || ""}</div>
},
}, },
{ {
accessorFn: (row) => row.n, accessorFn: (row) => row.n,
@@ -386,8 +382,6 @@ export default function DisksTable({ systemId }: { systemId: string }) {
function DiskSheet({ disk, smartData, open, onOpenChange }: { disk: DiskInfo | null; smartData?: SmartData; open: boolean; onOpenChange: (open: boolean) => void }) { function DiskSheet({ disk, smartData, open, onOpenChange }: { disk: DiskInfo | null; smartData?: SmartData; open: boolean; onOpenChange: (open: boolean) => void }) {
if (!disk) return null if (!disk) return null
const [sorting, setSorting] = React.useState<SortingState>([{ id: "id", desc: false }])
const smartAttributes = smartData?.a || [] const smartAttributes = smartData?.a || []
// Find all attributes where when failed is not empty // Find all attributes where when failed is not empty
@@ -411,11 +405,6 @@ function DiskSheet({ disk, smartData, open, onOpenChange }: { disk: DiskInfo | n
data: smartAttributes, data: smartAttributes,
columns: visibleColumns, columns: visibleColumns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
onSortingChange: setSorting,
state: {
sorting,
}
}) })
return ( return (

View File

@@ -145,7 +145,7 @@
} }
@utility container { @utility container {
@apply max-w-360 mx-auto px-4; @apply max-w-370 mx-auto px-4;
} }
@utility link { @utility link {

View File

@@ -571,6 +571,11 @@ else
echo "Adding beszel to docker group" echo "Adding beszel to docker group"
usermod -aG docker beszel usermod -aG docker beszel
fi fi
# Add the user to the disk group to allow access to disk devices if group disk exists
if getent group disk; then
echo "Adding beszel to disk group"
usermod -aG disk beszel
fi
fi fi
# Create the directory for the Beszel Agent # Create the directory for the Beszel Agent