mirror of
https://github.com/fankes/beszel.git
synced 2025-10-21 10:49:28 +08:00
Merge branch 'theRealBassist-add-agent-version-info'
This commit is contained in:
@@ -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"
|
||||||
@@ -140,9 +141,10 @@ func (a *Agent) getSystemStats() (*system.Info, *system.Stats) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
systemInfo := &system.Info{
|
systemInfo := &system.Info{
|
||||||
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) {
|
||||||
|
@@ -41,10 +41,11 @@ type Info struct {
|
|||||||
Threads int `json:"t"`
|
Threads int `json:"t"`
|
||||||
CpuModel string `json:"m"`
|
CpuModel string `json:"m"`
|
||||||
// Os string `json:"o"`
|
// Os string `json:"o"`
|
||||||
Uptime uint64 `json:"u"`
|
Uptime uint64 `json:"u"`
|
||||||
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
|
||||||
|
@@ -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")
|
||||||
|
@@ -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)
|
||||||
|
@@ -1,30 +1,57 @@
|
|||||||
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>
|
<>
|
||||||
<CardHeader className="pb-2 md:pb-5 px-4 sm:px-7 max-sm:pt-5">
|
<Card>
|
||||||
<CardTitle className="mb-1.5">All Systems</CardTitle>
|
<CardHeader className="pb-2 md:pb-5 px-4 sm:px-7 max-sm:pt-5">
|
||||||
<CardDescription>
|
<CardTitle className="mb-1.5">All Systems</CardTitle>
|
||||||
Updated in real time. Press{' '}
|
<CardDescription>
|
||||||
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-0.5 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100">
|
Updated in real time. Press{' '}
|
||||||
<span className="text-xs">⌘</span>K
|
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-0.5 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100">
|
||||||
</kbd>{' '}
|
<span className="text-xs">⌘</span>K
|
||||||
to open the command palette.
|
</kbd>{' '}
|
||||||
</CardDescription>
|
to open the command palette.
|
||||||
</CardHeader>
|
</CardDescription>
|
||||||
<CardContent className="max-sm:p-2">
|
</CardHeader>
|
||||||
<Suspense>
|
<CardContent className="max-sm:p-2">
|
||||||
<SystemsTable />
|
<Suspense>
|
||||||
</Suspense>
|
<SystemsTable />
|
||||||
</CardContent>
|
</Suspense>
|
||||||
</Card>
|
</CardContent>
|
||||||
|
</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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -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,
|
||||||
|
@@ -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>
|
||||||
|
@@ -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
|
||||||
|
1
beszel/site/src/types.d.ts
vendored
1
beszel/site/src/types.d.ts
vendored
@@ -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 {
|
||||||
|
Reference in New Issue
Block a user