Merge branch 'theRealBassist-add-agent-version-info'

This commit is contained in:
Henry Dollman
2024-08-20 14:04:37 -04:00
9 changed files with 114 additions and 70 deletions

View File

@@ -1,6 +1,7 @@
package agent package agent
import ( import (
"beszel"
"beszel/internal/entities/container" "beszel/internal/entities/container"
"beszel/internal/entities/system" "beszel/internal/entities/system"
"bytes" "bytes"
@@ -143,6 +144,7 @@ func (a *Agent) getSystemStats() (*system.Info, *system.Stats) {
Cpu: systemStats.Cpu, Cpu: systemStats.Cpu,
MemPct: systemStats.MemPct, MemPct: systemStats.MemPct,
DiskPct: systemStats.DiskPct, DiskPct: systemStats.DiskPct,
AgentVersion: beszel.Version,
} }
// add host info // add host info
@@ -162,7 +164,6 @@ func (a *Agent) getSystemStats() (*system.Info, *system.Stats) {
} }
return systemInfo, systemStats return systemInfo, systemStats
} }
func (a *Agent) getDockerStats() ([]*container.Stats, error) { func (a *Agent) getDockerStats() ([]*container.Stats, error) {

View File

@@ -45,6 +45,7 @@ type Info struct {
Cpu float64 `json:"cpu"` Cpu float64 `json:"cpu"`
MemPct float64 `json:"mp"` MemPct float64 `json:"mp"`
DiskPct float64 `json:"dp"` DiskPct float64 `json:"dp"`
AgentVersion string `json:"v"`
} }
// Final data structure to return to the hub // Final data structure to return to the hub

View File

@@ -1,6 +1,7 @@
package hub package hub
import ( import (
"beszel"
"beszel/internal/alerts" "beszel/internal/alerts"
"beszel/internal/entities/system" "beszel/internal/entities/system"
"beszel/internal/records" "beszel/internal/records"
@@ -34,6 +35,7 @@ type Hub struct {
connectionLock *sync.Mutex connectionLock *sync.Mutex
systemConnections map[string]*ssh.Client systemConnections map[string]*ssh.Client
sshClientConfig *ssh.ClientConfig sshClientConfig *ssh.ClientConfig
pubKey string
} }
func NewHub(app *pocketbase.PocketBase) *Hub { func NewHub(app *pocketbase.PocketBase) *Hub {
@@ -127,11 +129,7 @@ func (h *Hub) Run() {
if requestData.AuthRecord == nil { if requestData.AuthRecord == nil {
return apis.NewForbiddenError("Forbidden", nil) return apis.NewForbiddenError("Forbidden", nil)
} }
key, err := os.ReadFile(h.app.DataDir() + "/id_ed25519.pub") return c.JSON(http.StatusOK, map[string]string{"key": h.pubKey, "v": beszel.Version})
if err != nil {
return err
}
return c.JSON(http.StatusOK, map[string]string{"key": strings.TrimSuffix(string(key), "\n")})
}) })
// check if first time setup on login page // check if first time setup on login page
e.Router.GET("/api/beszel/first-run", func(c echo.Context) error { e.Router.GET("/api/beszel/first-run", func(c echo.Context) error {
@@ -384,6 +382,10 @@ func (h *Hub) getSSHKey() ([]byte, error) {
// check if the key pair already exists // check if the key pair already exists
existingKey, err := os.ReadFile(dataDir + "/id_ed25519") existingKey, err := os.ReadFile(dataDir + "/id_ed25519")
if err == nil { if err == nil {
if pubKey, err := os.ReadFile(h.app.DataDir() + "/id_ed25519.pub"); err == nil {
h.pubKey = strings.TrimSuffix(string(pubKey), "\n")
}
// return existing private key
return existingKey, nil return existingKey, nil
} }
@@ -421,6 +423,7 @@ func (h *Hub) getSSHKey() ([]byte, error) {
} }
pubKeyBytes := ssh.MarshalAuthorizedKey(publicKey) pubKeyBytes := ssh.MarshalAuthorizedKey(publicKey)
h.pubKey = strings.TrimSuffix(string(pubKeyBytes), "\n")
// Save the public key to a file // Save the public key to a file
publicFile, err := os.Create(dataDir + "/id_ed25519.pub") publicFile, err := os.Create(dataDir + "/id_ed25519.pub")

View File

@@ -14,7 +14,7 @@ import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { $publicKey, pb } from '@/lib/stores' import { $publicKey, pb } from '@/lib/stores'
import { Copy, Plus } from 'lucide-react' import { Copy, Plus } from 'lucide-react'
import { useState, useRef, MutableRefObject, useEffect } from 'react' import { useState, useRef, MutableRefObject } from 'react'
import { useStore } from '@nanostores/react' import { useStore } from '@nanostores/react'
import { copyToClipboard } from '@/lib/utils' import { copyToClipboard } from '@/lib/utils'
@@ -38,16 +38,6 @@ export function AddSystemButton() {
# FILESYSTEM: /dev/sda1 # set to the correct filesystem for disk I/O stats`) # FILESYSTEM: /dev/sda1 # set to the correct filesystem for disk I/O stats`)
} }
useEffect(() => {
if (publicKey || !open) {
return
}
// get public key
pb.send('/api/beszel/getkey', {}).then(({ key }) => {
$publicKey.set(key)
})
}, [open])
async function handleSubmit(e: SubmitEvent) { async function handleSubmit(e: SubmitEvent) {
e.preventDefault() e.preventDefault()
const formData = new FormData(e.target as HTMLFormElement) const formData = new FormData(e.target as HTMLFormElement)

View File

@@ -1,14 +1,21 @@
import { Suspense, lazy, useEffect } from 'react' import { Suspense, lazy, useEffect } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card'
import { $hubVersion } from '@/lib/stores'
import { useStore } from '@nanostores/react'
import { GithubIcon } from 'lucide-react'
import { Separator } from '../ui/separator'
const SystemsTable = lazy(() => import('../systems-table/systems-table')) const SystemsTable = lazy(() => import('../systems-table/systems-table'))
export default function () { export default function () {
const hubVersion = useStore($hubVersion)
useEffect(() => { useEffect(() => {
document.title = 'Dashboard / Beszel' document.title = 'Dashboard / Beszel'
}, []) }, [])
return ( return (
<>
<Card> <Card>
<CardHeader className="pb-2 md:pb-5 px-4 sm:px-7 max-sm:pt-5"> <CardHeader className="pb-2 md:pb-5 px-4 sm:px-7 max-sm:pt-5">
<CardTitle className="mb-1.5">All Systems</CardTitle> <CardTitle className="mb-1.5">All Systems</CardTitle>
@@ -26,5 +33,25 @@ export default function () {
</Suspense> </Suspense>
</CardContent> </CardContent>
</Card> </Card>
{hubVersion && (
<div className="flex gap-1.5 justify-end items-center pr-3 sm:pr-7 mt-3.5 text-xs opacity-80">
<a
href="https://github.com/henrygd/beszel"
target="_blank"
className="flex items-center gap-0.5 text-muted-foreground hover:text-foreground duration-75"
>
<GithubIcon className="h-3 w-3" /> GitHub
</a>
<Separator orientation="vertical" className="h-3 bg-muted-foreground" />
<a
href="https://github.com/henrygd/beszel/releases"
target="_blank"
className="text-muted-foreground hover:text-foreground duration-75"
>
Beszel {hubVersion}
</a>
</div>
)}
</>
) )
} }

View File

@@ -55,9 +55,10 @@ import {
PauseCircleIcon, PauseCircleIcon,
PlayCircleIcon, PlayCircleIcon,
Trash2Icon, Trash2Icon,
WifiIcon,
} from 'lucide-react' } from 'lucide-react'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { $systems, pb } from '@/lib/stores' import { $hubVersion, $systems, pb } from '@/lib/stores'
import { useStore } from '@nanostores/react' import { useStore } from '@nanostores/react'
import { AddSystemButton } from '../add-system' import { AddSystemButton } from '../add-system'
import { cn, copyToClipboard, isReadOnlyUser } from '@/lib/utils' import { cn, copyToClipboard, isReadOnlyUser } from '@/lib/utils'
@@ -82,7 +83,12 @@ function CellFormatter(info: CellContext<SystemRecord, unknown>) {
) )
} }
function sortableHeader(column: Column<SystemRecord, unknown>, name: string, Icon: any) { function sortableHeader(
column: Column<SystemRecord, unknown>,
name: string,
Icon: any,
hideSortIcon = false
) {
return ( return (
<Button <Button
variant="ghost" variant="ghost"
@@ -91,13 +97,14 @@ function sortableHeader(column: Column<SystemRecord, unknown>, name: string, Ico
> >
<Icon className="mr-2 h-4 w-4" /> <Icon className="mr-2 h-4 w-4" />
{name} {name}
<ArrowUpDown className="ml-2 h-4 w-4" /> {!hideSortIcon && <ArrowUpDown className="ml-2 h-4 w-4" />}
</Button> </Button>
) )
} }
export default function SystemsTable() { export default function SystemsTable() {
const data = useStore($systems) const data = useStore($systems)
const hubVersion = useStore($hubVersion)
const [sorting, setSorting] = useState<SortingState>([]) const [sorting, setSorting] = useState<SortingState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]) const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
@@ -150,6 +157,29 @@ export default function SystemsTable() {
cell: CellFormatter, cell: CellFormatter,
header: ({ column }) => sortableHeader(column, 'Disk', HardDrive), header: ({ column }) => sortableHeader(column, 'Disk', HardDrive),
}, },
{
accessorKey: 'info.v',
size: 50,
cell: (info) => {
const version = info.getValue() as string
if (!version || !hubVersion) {
return null
}
return (
<span className="flex gap-2 items-center md:pr-5 tabular-nums pl-1.5">
<span
className={cn(
'w-2 h-2 left-0 rounded-full',
version === hubVersion ? 'bg-green-500' : 'bg-yellow-500'
)}
style={{ marginBottom: '-1px' }}
></span>
<span>{info.getValue() as string}</span>
</span>
)
},
header: ({ column }) => sortableHeader(column, 'Agent', WifiIcon, true),
},
{ {
id: 'actions', id: 'actions',
size: 120, size: 120,

View File

@@ -20,5 +20,8 @@ export const $alerts = atom([] as AlertRecord[])
/** SSH public key */ /** SSH public key */
export const $publicKey = atom('') export const $publicKey = atom('')
/** Beszel hub version */
export const $hubVersion = atom('')
/** Chart time period */ /** Chart time period */
export const $chartTime = atom('1h') as WritableAtom<ChartTimes> export const $chartTime = atom('1h') as WritableAtom<ChartTimes>

View File

@@ -3,7 +3,15 @@ import React, { Suspense, lazy, useEffect } from 'react'
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import Home from './components/routes/home.tsx' import Home from './components/routes/home.tsx'
import { ThemeProvider } from './components/theme-provider.tsx' import { ThemeProvider } from './components/theme-provider.tsx'
import { $alerts, $authenticated, $updatedSystem, $systems, pb } from './lib/stores.ts' import {
$alerts,
$authenticated,
$updatedSystem,
$systems,
pb,
$publicKey,
$hubVersion,
} from './lib/stores.ts'
import { ModeToggle } from './components/mode-toggle.tsx' import { ModeToggle } from './components/mode-toggle.tsx'
import { import {
cn, cn,
@@ -16,7 +24,6 @@ import {
import { buttonVariants } from './components/ui/button.tsx' import { buttonVariants } from './components/ui/button.tsx'
import { import {
DatabaseBackupIcon, DatabaseBackupIcon,
GithubIcon,
LockKeyholeIcon, LockKeyholeIcon,
LogOutIcon, LogOutIcon,
LogsIcon, LogsIcon,
@@ -27,12 +34,6 @@ import {
import { useStore } from '@nanostores/react' import { useStore } from '@nanostores/react'
import { Toaster } from './components/ui/toaster.tsx' import { Toaster } from './components/ui/toaster.tsx'
import { Logo } from './components/logo.tsx' import { Logo } from './components/logo.tsx'
import {
TooltipProvider,
Tooltip,
TooltipTrigger,
TooltipContent,
} from '@/components/ui/tooltip.tsx'
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -60,6 +61,11 @@ const App = () => {
pb.authStore.onChange(() => { pb.authStore.onChange(() => {
$authenticated.set(pb.authStore.isValid) $authenticated.set(pb.authStore.isValid)
}) })
// get version / public key
pb.send('/api/beszel/getkey', {}).then(({ key, v }) => {
$publicKey.set(key)
$hubVersion.set(v)
})
// get servers / alerts // get servers / alerts
updateSystemList() updateSystemList()
updateAlerts() updateAlerts()
@@ -137,24 +143,6 @@ const Layout = () => {
<div className={'flex ml-auto'}> <div className={'flex ml-auto'}>
<ModeToggle /> <ModeToggle />
<TooltipProvider delayDuration={300}>
<Tooltip>
<TooltipTrigger asChild>
<a
title={'Github'}
aria-label="Github repo"
href={'https://github.com/henrygd/beszel'}
target="_blank"
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
>
<GithubIcon className="h-[1.2rem] w-[1.2rem]" />
</a>
</TooltipTrigger>
<TooltipContent>
<p>Github Repository</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<button <button

View File

@@ -6,6 +6,7 @@ export interface SystemRecord extends RecordModel {
status: 'up' | 'down' | 'paused' | 'pending' status: 'up' | 'down' | 'paused' | 'pending'
port: string port: string
info: SystemInfo info: SystemInfo
v: string
} }
export interface SystemInfo { export interface SystemInfo {