Add host agent multi-platform binary distribution and improve host details UI
- Build host agent binaries for all platforms (linux/darwin/windows, amd64/arm64/armv7) in Docker - Add Makefile target for building agent binaries locally - Add startup validation to check for missing agent binaries - Improve download endpoint error messages with troubleshooting guidance - Enhance host details drawer layout with better organization and visual hierarchy - Update base images to rolling versions (node:20-alpine, golang:1.24-alpine, alpine:3.20)
This commit is contained in:
parent
7dd7a0b0f9
commit
fdf0977be2
5 changed files with 160 additions and 48 deletions
49
Dockerfile
49
Dockerfile
|
|
@ -2,7 +2,7 @@
|
||||||
ARG BUILD_AGENT=1
|
ARG BUILD_AGENT=1
|
||||||
|
|
||||||
# Build stage for frontend (must be built first for embedding)
|
# Build stage for frontend (must be built first for embedding)
|
||||||
FROM node:20.16.0-alpine3.19 AS frontend-builder
|
FROM node:20-alpine AS frontend-builder
|
||||||
|
|
||||||
WORKDIR /app/frontend-modern
|
WORKDIR /app/frontend-modern
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ RUN --mount=type=cache,id=pulse-npm-cache,target=/root/.npm \
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
# Build stage for Go backend
|
# Build stage for Go backend
|
||||||
FROM golang:1.24.9-alpine3.19 AS backend-builder
|
FROM golang:1.24-alpine AS backend-builder
|
||||||
|
|
||||||
ARG BUILD_AGENT
|
ARG BUILD_AGENT
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
@ -77,6 +77,34 @@ RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \
|
||||||
fi && \
|
fi && \
|
||||||
cp pulse-docker-agent-linux-amd64 pulse-docker-agent
|
cp pulse-docker-agent-linux-amd64 pulse-docker-agent
|
||||||
|
|
||||||
|
# Build host-agent binaries for all platforms (for download endpoint)
|
||||||
|
RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \
|
||||||
|
--mount=type=cache,id=pulse-go-build,target=/root/.cache/go-build \
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
|
||||||
|
-ldflags="-s -w" \
|
||||||
|
-trimpath \
|
||||||
|
-o pulse-host-agent-linux-amd64 ./cmd/pulse-host-agent && \
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build \
|
||||||
|
-ldflags="-s -w" \
|
||||||
|
-trimpath \
|
||||||
|
-o pulse-host-agent-linux-arm64 ./cmd/pulse-host-agent && \
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build \
|
||||||
|
-ldflags="-s -w" \
|
||||||
|
-trimpath \
|
||||||
|
-o pulse-host-agent-linux-armv7 ./cmd/pulse-host-agent && \
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build \
|
||||||
|
-ldflags="-s -w" \
|
||||||
|
-trimpath \
|
||||||
|
-o pulse-host-agent-darwin-amd64 ./cmd/pulse-host-agent && \
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build \
|
||||||
|
-ldflags="-s -w" \
|
||||||
|
-trimpath \
|
||||||
|
-o pulse-host-agent-darwin-arm64 ./cmd/pulse-host-agent && \
|
||||||
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build \
|
||||||
|
-ldflags="-s -w" \
|
||||||
|
-trimpath \
|
||||||
|
-o pulse-host-agent-windows-amd64.exe ./cmd/pulse-host-agent
|
||||||
|
|
||||||
# Build pulse-sensor-proxy
|
# Build pulse-sensor-proxy
|
||||||
RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \
|
RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \
|
||||||
--mount=type=cache,id=pulse-go-build,target=/root/.cache/go-build \
|
--mount=type=cache,id=pulse-go-build,target=/root/.cache/go-build \
|
||||||
|
|
@ -86,7 +114,7 @@ RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \
|
||||||
-o pulse-sensor-proxy ./cmd/pulse-sensor-proxy
|
-o pulse-sensor-proxy ./cmd/pulse-sensor-proxy
|
||||||
|
|
||||||
# Runtime image for the Docker agent (offered via --target agent_runtime)
|
# Runtime image for the Docker agent (offered via --target agent_runtime)
|
||||||
FROM alpine:3.19.1 AS agent_runtime
|
FROM alpine:3.20 AS agent_runtime
|
||||||
|
|
||||||
# Use TARGETARCH to select the correct binary for the build platform
|
# Use TARGETARCH to select the correct binary for the build platform
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
@ -118,7 +146,7 @@ ENV PULSE_NO_AUTO_UPDATE=true
|
||||||
ENTRYPOINT ["/usr/local/bin/pulse-docker-agent"]
|
ENTRYPOINT ["/usr/local/bin/pulse-docker-agent"]
|
||||||
|
|
||||||
# Final stage (Pulse server runtime)
|
# Final stage (Pulse server runtime)
|
||||||
FROM alpine:3.19.1
|
FROM alpine:3.20
|
||||||
|
|
||||||
RUN apk --no-cache add ca-certificates tzdata su-exec openssh-client
|
RUN apk --no-cache add ca-certificates tzdata su-exec openssh-client
|
||||||
|
|
||||||
|
|
@ -138,9 +166,10 @@ RUN chmod +x /docker-entrypoint.sh
|
||||||
# Provide installer scripts for HTTP download endpoints
|
# Provide installer scripts for HTTP download endpoints
|
||||||
RUN mkdir -p /opt/pulse/scripts
|
RUN mkdir -p /opt/pulse/scripts
|
||||||
COPY scripts/install-docker-agent.sh /opt/pulse/scripts/install-docker-agent.sh
|
COPY scripts/install-docker-agent.sh /opt/pulse/scripts/install-docker-agent.sh
|
||||||
|
COPY scripts/install-host-agent.sh /opt/pulse/scripts/install-host-agent.sh
|
||||||
COPY scripts/install-sensor-proxy.sh /opt/pulse/scripts/install-sensor-proxy.sh
|
COPY scripts/install-sensor-proxy.sh /opt/pulse/scripts/install-sensor-proxy.sh
|
||||||
COPY scripts/install-docker.sh /opt/pulse/scripts/install-docker.sh
|
COPY scripts/install-docker.sh /opt/pulse/scripts/install-docker.sh
|
||||||
RUN chmod 755 /opt/pulse/scripts/install-docker-agent.sh /opt/pulse/scripts/install-sensor-proxy.sh /opt/pulse/scripts/install-docker.sh
|
RUN chmod 755 /opt/pulse/scripts/install-docker-agent.sh /opt/pulse/scripts/install-host-agent.sh /opt/pulse/scripts/install-sensor-proxy.sh /opt/pulse/scripts/install-docker.sh
|
||||||
|
|
||||||
# Copy multi-arch docker-agent binaries for download endpoint
|
# Copy multi-arch docker-agent binaries for download endpoint
|
||||||
RUN mkdir -p /opt/pulse/bin
|
RUN mkdir -p /opt/pulse/bin
|
||||||
|
|
@ -149,6 +178,16 @@ COPY --from=backend-builder /app/pulse-docker-agent-linux-arm64 /opt/pulse/bin/
|
||||||
COPY --from=backend-builder /app/pulse-docker-agent-linux-armv7 /opt/pulse/bin/
|
COPY --from=backend-builder /app/pulse-docker-agent-linux-armv7 /opt/pulse/bin/
|
||||||
COPY --from=backend-builder /app/pulse-docker-agent /opt/pulse/bin/pulse-docker-agent
|
COPY --from=backend-builder /app/pulse-docker-agent /opt/pulse/bin/pulse-docker-agent
|
||||||
|
|
||||||
|
# Copy multi-arch host-agent binaries for download endpoint
|
||||||
|
COPY --from=backend-builder /app/pulse-host-agent-linux-amd64 /opt/pulse/bin/
|
||||||
|
COPY --from=backend-builder /app/pulse-host-agent-linux-arm64 /opt/pulse/bin/
|
||||||
|
COPY --from=backend-builder /app/pulse-host-agent-linux-armv7 /opt/pulse/bin/
|
||||||
|
COPY --from=backend-builder /app/pulse-host-agent-darwin-amd64 /opt/pulse/bin/
|
||||||
|
COPY --from=backend-builder /app/pulse-host-agent-darwin-arm64 /opt/pulse/bin/
|
||||||
|
COPY --from=backend-builder /app/pulse-host-agent-windows-amd64.exe /opt/pulse/bin/
|
||||||
|
# Create symlink for Windows without .exe extension
|
||||||
|
RUN ln -s pulse-host-agent-windows-amd64.exe /opt/pulse/bin/pulse-host-agent-windows-amd64
|
||||||
|
|
||||||
# Copy pulse-sensor-proxy binary for download endpoint
|
# Copy pulse-sensor-proxy binary for download endpoint
|
||||||
COPY --from=backend-builder /app/pulse-sensor-proxy /opt/pulse/bin/pulse-sensor-proxy
|
COPY --from=backend-builder /app/pulse-sensor-proxy /opt/pulse/bin/pulse-sensor-proxy
|
||||||
|
|
||||||
|
|
|
||||||
19
Makefile
19
Makefile
|
|
@ -1,13 +1,13 @@
|
||||||
# Pulse Makefile for development
|
# Pulse Makefile for development
|
||||||
|
|
||||||
.PHONY: build run dev frontend backend all clean distclean dev-hot lint lint-backend lint-frontend format format-backend format-frontend
|
.PHONY: build run dev frontend backend all clean distclean dev-hot lint lint-backend lint-frontend format format-backend format-frontend build-agents
|
||||||
|
|
||||||
FRONTEND_DIR := frontend-modern
|
FRONTEND_DIR := frontend-modern
|
||||||
FRONTEND_DIST := $(FRONTEND_DIR)/dist
|
FRONTEND_DIST := $(FRONTEND_DIR)/dist
|
||||||
FRONTEND_EMBED_DIR := internal/api/frontend-modern
|
FRONTEND_EMBED_DIR := internal/api/frontend-modern
|
||||||
|
|
||||||
# Build everything
|
# Build everything (including all agent binaries)
|
||||||
all: frontend backend
|
all: frontend backend build-agents
|
||||||
|
|
||||||
# Build frontend only
|
# Build frontend only
|
||||||
frontend:
|
frontend:
|
||||||
|
|
@ -68,3 +68,16 @@ format-backend:
|
||||||
|
|
||||||
format-frontend:
|
format-frontend:
|
||||||
npm --prefix $(FRONTEND_DIR) run format
|
npm --prefix $(FRONTEND_DIR) run format
|
||||||
|
|
||||||
|
# Build all host agent binaries for all platforms
|
||||||
|
build-agents:
|
||||||
|
@echo "Building host agent binaries for all platforms..."
|
||||||
|
@mkdir -p bin
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-linux-amd64 ./cmd/pulse-host-agent
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-linux-arm64 ./cmd/pulse-host-agent
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-linux-armv7 ./cmd/pulse-host-agent
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-darwin-amd64 ./cmd/pulse-host-agent
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-darwin-arm64 ./cmd/pulse-host-agent
|
||||||
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-windows-amd64.exe ./cmd/pulse-host-agent
|
||||||
|
@ln -sf pulse-host-agent-windows-amd64.exe bin/pulse-host-agent-windows-amd64
|
||||||
|
@echo "✓ All host agent binaries built in bin/"
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,9 @@ func runServer() {
|
||||||
|
|
||||||
log.Info().Msg("Starting Pulse monitoring server")
|
log.Info().Msg("Starting Pulse monitoring server")
|
||||||
|
|
||||||
|
// Validate agent binaries are available for download
|
||||||
|
validateAgentBinaries()
|
||||||
|
|
||||||
// Create context that cancels on interrupt
|
// Create context that cancels on interrupt
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
@ -345,3 +348,46 @@ func performAutoImport() error {
|
||||||
log.Info().Msg("Configuration auto-imported successfully")
|
log.Info().Msg("Configuration auto-imported successfully")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateAgentBinaries checks if agent binaries are available for download
|
||||||
|
// and logs warnings if any are missing
|
||||||
|
func validateAgentBinaries() {
|
||||||
|
binDirs := []string{"/opt/pulse/bin", "./bin", "."}
|
||||||
|
platforms := []struct {
|
||||||
|
name string
|
||||||
|
file string
|
||||||
|
}{
|
||||||
|
{"linux-amd64", "pulse-host-agent-linux-amd64"},
|
||||||
|
{"linux-arm64", "pulse-host-agent-linux-arm64"},
|
||||||
|
{"linux-armv7", "pulse-host-agent-linux-armv7"},
|
||||||
|
{"darwin-amd64", "pulse-host-agent-darwin-amd64"},
|
||||||
|
{"darwin-arm64", "pulse-host-agent-darwin-arm64"},
|
||||||
|
{"windows-amd64", "pulse-host-agent-windows-amd64"},
|
||||||
|
}
|
||||||
|
|
||||||
|
missing := []string{}
|
||||||
|
searchedPaths := []string{}
|
||||||
|
|
||||||
|
for _, platform := range platforms {
|
||||||
|
found := false
|
||||||
|
for _, dir := range binDirs {
|
||||||
|
path := filepath.Join(dir, platform.file)
|
||||||
|
searchedPaths = append(searchedPaths, path)
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
missing = append(missing, platform.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missing) > 0 {
|
||||||
|
log.Warn().
|
||||||
|
Strs("missing_platforms", missing).
|
||||||
|
Msg("Host agent binaries missing - install script downloads will fail. Rebuild Docker image or run build-release.sh to generate all platform binaries.")
|
||||||
|
} else {
|
||||||
|
log.Info().Msg("All host agent binaries available for download")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -333,41 +333,41 @@ export const HostsOverview: Component<HostsOverviewProps> = (props) => {
|
||||||
|
|
||||||
{/* Drawer - Additional Info */}
|
{/* Drawer - Additional Info */}
|
||||||
<Show when={drawerOpen() && hasDrawerContent()}>
|
<Show when={drawerOpen() && hasDrawerContent()}>
|
||||||
<tr class="text-[11px] bg-gray-50/60 text-gray-600 dark:bg-gray-800/40 dark:text-gray-300">
|
<tr class="bg-gray-50 dark:bg-gray-900/50">
|
||||||
<td class="px-4 py-2" colSpan={5}>
|
<td class="px-4 py-3" colSpan={5}>
|
||||||
<div class="grid w-full gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
<div class="flex flex-wrap justify-start gap-3">
|
||||||
{/* System Info */}
|
{/* System Info */}
|
||||||
<div class="rounded border border-gray-200 bg-white/70 p-2 shadow-sm dark:border-gray-600/70 dark:bg-gray-900/30">
|
<div class="min-w-[220px] flex-1 rounded border border-gray-200 bg-white/70 p-2 shadow-sm dark:border-gray-600/70 dark:bg-gray-900/30">
|
||||||
<div class="text-[11px] font-medium text-gray-700 dark:text-gray-200">System</div>
|
<div class="text-[11px] font-medium uppercase tracking-wide text-gray-700 dark:text-gray-200">System</div>
|
||||||
<div class="mt-1 space-y-1 text-gray-600 dark:text-gray-300">
|
<div class="mt-2 space-y-1 text-[11px] text-gray-600 dark:text-gray-300">
|
||||||
<Show when={host.cpuCount}>
|
<Show when={host.cpuCount}>
|
||||||
<div class="flex items-start gap-1">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<span class="text-gray-500 dark:text-gray-400 min-w-[60px]">CPUs:</span>
|
<span class="font-medium text-gray-700 dark:text-gray-200">CPUs</span>
|
||||||
<span>{host.cpuCount}</span>
|
<span class="text-right text-gray-600 dark:text-gray-300">{host.cpuCount}</span>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={host.loadAverage && host.loadAverage.length > 0}>
|
<Show when={host.loadAverage && host.loadAverage.length > 0}>
|
||||||
<div class="flex items-start gap-1">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<span class="text-gray-500 dark:text-gray-400 min-w-[60px]">Load Avg:</span>
|
<span class="font-medium text-gray-700 dark:text-gray-200">Load Avg</span>
|
||||||
<span>{host.loadAverage!.map(l => l.toFixed(2)).join(', ')}</span>
|
<span class="text-right text-gray-600 dark:text-gray-300">{host.loadAverage!.map(l => l.toFixed(2)).join(', ')}</span>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={host.architecture}>
|
<Show when={host.architecture}>
|
||||||
<div class="flex items-start gap-1">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<span class="text-gray-500 dark:text-gray-400 min-w-[60px]">Arch:</span>
|
<span class="font-medium text-gray-700 dark:text-gray-200">Arch</span>
|
||||||
<span>{host.architecture}</span>
|
<span class="text-right text-gray-600 dark:text-gray-300">{host.architecture}</span>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={host.kernelVersion}>
|
<Show when={host.kernelVersion}>
|
||||||
<div class="flex items-start gap-1">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<span class="text-gray-500 dark:text-gray-400 min-w-[60px]">Kernel:</span>
|
<span class="font-medium text-gray-700 dark:text-gray-200">Kernel</span>
|
||||||
<span class="break-all">{host.kernelVersion}</span>
|
<span class="text-right text-gray-600 dark:text-gray-300 truncate">{host.kernelVersion}</span>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={host.agentVersion}>
|
<Show when={host.agentVersion}>
|
||||||
<div class="flex items-start gap-1">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<span class="text-gray-500 dark:text-gray-400 min-w-[60px]">Agent:</span>
|
<span class="font-medium text-gray-700 dark:text-gray-200">Agent</span>
|
||||||
<span>{host.agentVersion}</span>
|
<span class="text-right text-gray-600 dark:text-gray-300">{host.agentVersion}</span>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -375,15 +375,15 @@ export const HostsOverview: Component<HostsOverviewProps> = (props) => {
|
||||||
|
|
||||||
{/* Network Interfaces */}
|
{/* Network Interfaces */}
|
||||||
<Show when={host.networkInterfaces && host.networkInterfaces.length > 0}>
|
<Show when={host.networkInterfaces && host.networkInterfaces.length > 0}>
|
||||||
<div class="rounded border border-gray-200 bg-white/70 p-2 shadow-sm dark:border-gray-600/70 dark:bg-gray-900/30">
|
<div class="min-w-[220px] flex-1 rounded border border-gray-200 bg-white/70 p-2 shadow-sm dark:border-gray-600/70 dark:bg-gray-900/30">
|
||||||
<div class="text-[11px] font-medium text-gray-700 dark:text-gray-200">Network</div>
|
<div class="text-[11px] font-medium uppercase tracking-wide text-gray-700 dark:text-gray-200">Network</div>
|
||||||
<div class="mt-1 space-y-1">
|
<div class="mt-2 space-y-2 text-[11px]">
|
||||||
<For each={host.networkInterfaces?.slice(0, 4)}>
|
<For each={host.networkInterfaces?.slice(0, 4)}>
|
||||||
{(iface) => (
|
{(iface) => (
|
||||||
<div class="text-gray-600 dark:text-gray-300">
|
<div class="rounded border border-dashed border-gray-200 p-2 dark:border-gray-700/70">
|
||||||
<div class="font-medium">{iface.name}</div>
|
<div class="font-medium text-gray-700 dark:text-gray-200">{iface.name}</div>
|
||||||
<Show when={iface.addresses && iface.addresses.length > 0}>
|
<Show when={iface.addresses && iface.addresses.length > 0}>
|
||||||
<div class="flex flex-wrap gap-1 mt-0.5">
|
<div class="flex flex-wrap gap-1 mt-1 text-[10px]">
|
||||||
<For each={iface.addresses}>
|
<For each={iface.addresses}>
|
||||||
{(addr) => (
|
{(addr) => (
|
||||||
<span class="rounded bg-blue-100 px-1.5 py-0.5 text-blue-700 dark:bg-blue-900/40 dark:text-blue-200">
|
<span class="rounded bg-blue-100 px-1.5 py-0.5 text-blue-700 dark:bg-blue-900/40 dark:text-blue-200">
|
||||||
|
|
@ -402,22 +402,22 @@ export const HostsOverview: Component<HostsOverviewProps> = (props) => {
|
||||||
|
|
||||||
{/* Disk Info */}
|
{/* Disk Info */}
|
||||||
<Show when={host.disks && host.disks.length > 0}>
|
<Show when={host.disks && host.disks.length > 0}>
|
||||||
<div class="rounded border border-gray-200 bg-white/70 p-2 shadow-sm dark:border-gray-600/70 dark:bg-gray-900/30">
|
<div class="min-w-[220px] flex-1 rounded border border-gray-200 bg-white/70 p-2 shadow-sm dark:border-gray-600/70 dark:bg-gray-900/30">
|
||||||
<div class="text-[11px] font-medium text-gray-700 dark:text-gray-200">Disks</div>
|
<div class="text-[11px] font-medium uppercase tracking-wide text-gray-700 dark:text-gray-200">Disks</div>
|
||||||
<div class="mt-1 space-y-1">
|
<div class="mt-2 space-y-2 text-[11px]">
|
||||||
<For each={host.disks?.slice(0, 3)}>
|
<For each={host.disks?.slice(0, 3)}>
|
||||||
{(disk) => {
|
{(disk) => {
|
||||||
const diskPercent = () => disk.usage ?? 0;
|
const diskPercent = () => disk.usage ?? 0;
|
||||||
return (
|
return (
|
||||||
<div class="text-gray-600 dark:text-gray-300">
|
<div class="rounded border border-dashed border-gray-200 p-2 dark:border-gray-700/70">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="font-medium truncate">{disk.mountpoint || disk.device}</span>
|
<span class="font-medium text-gray-700 dark:text-gray-200 truncate">{disk.mountpoint || disk.device}</span>
|
||||||
<span class="text-[10px] text-gray-500 dark:text-gray-400">
|
<span class="text-[10px] text-gray-500 dark:text-gray-400">
|
||||||
{formatBytes(disk.used ?? 0, 0)} / {formatBytes(disk.total ?? 0, 0)}
|
{formatBytes(disk.used ?? 0, 0)} / {formatBytes(disk.total ?? 0, 0)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Show when={diskPercent() > 0}>
|
<Show when={diskPercent() > 0}>
|
||||||
<div class="mt-0.5">
|
<div class="mt-1">
|
||||||
<MetricBar
|
<MetricBar
|
||||||
value={diskPercent()}
|
value={diskPercent()}
|
||||||
label={formatPercent(diskPercent())}
|
label={formatPercent(diskPercent())}
|
||||||
|
|
@ -435,14 +435,14 @@ export const HostsOverview: Component<HostsOverviewProps> = (props) => {
|
||||||
|
|
||||||
{/* Temperature Sensors */}
|
{/* Temperature Sensors */}
|
||||||
<Show when={host.sensors?.temperatureCelsius && Object.keys(host.sensors.temperatureCelsius).length > 0}>
|
<Show when={host.sensors?.temperatureCelsius && Object.keys(host.sensors.temperatureCelsius).length > 0}>
|
||||||
<div class="rounded border border-gray-200 bg-white/70 p-2 shadow-sm dark:border-gray-600/70 dark:bg-gray-900/30">
|
<div class="min-w-[220px] flex-1 rounded border border-gray-200 bg-white/70 p-2 shadow-sm dark:border-gray-600/70 dark:bg-gray-900/30">
|
||||||
<div class="text-[11px] font-medium text-gray-700 dark:text-gray-200">Temperatures</div>
|
<div class="text-[11px] font-medium uppercase tracking-wide text-gray-700 dark:text-gray-200">Temperatures</div>
|
||||||
<div class="mt-1 space-y-1 text-gray-600 dark:text-gray-300">
|
<div class="mt-2 space-y-1 text-[11px] text-gray-600 dark:text-gray-300">
|
||||||
<For each={Object.entries(host.sensors!.temperatureCelsius!).slice(0, 5)}>
|
<For each={Object.entries(host.sensors!.temperatureCelsius!).slice(0, 5)}>
|
||||||
{([name, temp]) => (
|
{([name, temp]) => (
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<span class="truncate text-[10px]">{name}</span>
|
<span class="font-medium text-gray-700 dark:text-gray-200 truncate">{name}</span>
|
||||||
<span class={temp > 80 ? 'text-red-600 dark:text-red-400 font-semibold' : ''}>
|
<span class={`text-right ${temp > 80 ? 'text-red-600 dark:text-red-400 font-semibold' : 'text-gray-600 dark:text-gray-300'}`}>
|
||||||
{temp.toFixed(1)}°C
|
{temp.toFixed(1)}°C
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3302,7 +3302,21 @@ func (r *Router) handleDownloadHostAgent(w http.ResponseWriter, req *http.Reques
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Error(w, "Host agent binary not found. Please build from source: go build ./cmd/pulse-host-agent", http.StatusNotFound)
|
// Build detailed error message with troubleshooting guidance
|
||||||
|
var errorMsg strings.Builder
|
||||||
|
errorMsg.WriteString(fmt.Sprintf("Host agent binary not found for %s/%s\n\n", platformParam, archParam))
|
||||||
|
errorMsg.WriteString("Troubleshooting:\n")
|
||||||
|
errorMsg.WriteString("1. If running in Docker: Rebuild the Docker image to include all platform binaries\n")
|
||||||
|
errorMsg.WriteString("2. If running bare metal: Run 'scripts/build-release.sh' to build all platform binaries\n")
|
||||||
|
errorMsg.WriteString("3. Build from source:\n")
|
||||||
|
errorMsg.WriteString(fmt.Sprintf(" GOOS=%s GOARCH=%s go build -o pulse-host-agent-%s-%s ./cmd/pulse-host-agent\n", platformParam, archParam, platformParam, archParam))
|
||||||
|
errorMsg.WriteString(fmt.Sprintf(" sudo mv pulse-host-agent-%s-%s /opt/pulse/bin/\n\n", platformParam, archParam))
|
||||||
|
errorMsg.WriteString("Searched locations:\n")
|
||||||
|
for _, path := range searchPaths {
|
||||||
|
errorMsg.WriteString(fmt.Sprintf(" - %s\n", path))
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Error(w, errorMsg.String(), http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveChecksum computes and serves the SHA256 checksum of a file
|
// serveChecksum computes and serves the SHA256 checksum of a file
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue