mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-22 21:46:18 +01:00
Compare commits
3 Commits
v0.5.2
...
built-in-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8de2dee4e9 | ||
|
|
7a82571921 | ||
|
|
e81f8ac387 |
@@ -70,13 +70,15 @@ func (a *Agent) Run(pubKey []byte, addr string) {
|
|||||||
|
|
||||||
// if debugging, print stats
|
// if debugging, print stats
|
||||||
if a.debug {
|
if a.debug {
|
||||||
slog.Debug("Stats", "data", a.gatherStats())
|
slog.Debug("Stats", "data", a.GatherStats())
|
||||||
}
|
}
|
||||||
|
|
||||||
a.startServer(pubKey, addr)
|
if pubKey != nil {
|
||||||
|
a.startServer(pubKey, addr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) gatherStats() system.CombinedData {
|
func (a *Agent) GatherStats() system.CombinedData {
|
||||||
systemData := system.CombinedData{
|
systemData := system.CombinedData{
|
||||||
Stats: a.getSystemStats(),
|
Stats: a.getSystemStats(),
|
||||||
Info: a.systemInfo,
|
Info: a.systemInfo,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func (a *Agent) startServer(pubKey []byte, addr string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) handleSession(s sshServer.Session) {
|
func (a *Agent) handleSession(s sshServer.Session) {
|
||||||
stats := a.gatherStats()
|
stats := a.GatherStats()
|
||||||
slog.Debug("Sending stats", "data", stats)
|
slog.Debug("Sending stats", "data", stats)
|
||||||
if err := json.NewEncoder(s).Encode(stats); err != nil {
|
if err := json.NewEncoder(s).Encode(stats); err != nil {
|
||||||
slog.Error("Error encoding stats", "err", err)
|
slog.Error("Error encoding stats", "err", err)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package hub
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"beszel"
|
"beszel"
|
||||||
|
"beszel/internal/agent"
|
||||||
"beszel/internal/alerts"
|
"beszel/internal/alerts"
|
||||||
"beszel/internal/entities/system"
|
"beszel/internal/entities/system"
|
||||||
"beszel/internal/records"
|
"beszel/internal/records"
|
||||||
@@ -42,6 +43,7 @@ type Hub struct {
|
|||||||
am *alerts.AlertManager
|
am *alerts.AlertManager
|
||||||
um *users.UserManager
|
um *users.UserManager
|
||||||
rm *records.RecordManager
|
rm *records.RecordManager
|
||||||
|
hubAgent *agent.Agent
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHub(app *pocketbase.PocketBase) *Hub {
|
func NewHub(app *pocketbase.PocketBase) *Hub {
|
||||||
@@ -56,10 +58,6 @@ func NewHub(app *pocketbase.PocketBase) *Hub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hub) Run() {
|
func (h *Hub) Run() {
|
||||||
// rm := records.NewRecordManager(h.app)
|
|
||||||
// am := alerts.NewAlertManager(h.app)
|
|
||||||
// um := users.NewUserManager(h.app)
|
|
||||||
|
|
||||||
// loosely check if it was executed using "go run"
|
// loosely check if it was executed using "go run"
|
||||||
isGoRun := strings.HasPrefix(os.Args[0], os.TempDir())
|
isGoRun := strings.HasPrefix(os.Args[0], os.TempDir())
|
||||||
|
|
||||||
@@ -73,25 +71,22 @@ func (h *Hub) Run() {
|
|||||||
// initial setup
|
// initial setup
|
||||||
h.app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
h.app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
// create ssh client config
|
// create ssh client config
|
||||||
err := h.createSSHClientConfig()
|
if err := h.createSSHClientConfig(); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
// set auth settings
|
// set auth settings
|
||||||
usersCollection, err := h.app.Dao().FindCollectionByNameOrId("users")
|
if usersCollection, err := h.app.Dao().FindCollectionByNameOrId("users"); err == nil {
|
||||||
if err != nil {
|
usersAuthOptions := usersCollection.AuthOptions()
|
||||||
return err
|
usersAuthOptions.AllowUsernameAuth = false
|
||||||
}
|
if os.Getenv("DISABLE_PASSWORD_AUTH") == "true" {
|
||||||
usersAuthOptions := usersCollection.AuthOptions()
|
usersAuthOptions.AllowEmailAuth = false
|
||||||
usersAuthOptions.AllowUsernameAuth = false
|
} else {
|
||||||
if os.Getenv("DISABLE_PASSWORD_AUTH") == "true" {
|
usersAuthOptions.AllowEmailAuth = true
|
||||||
usersAuthOptions.AllowEmailAuth = false
|
}
|
||||||
} else {
|
usersCollection.SetOptions(usersAuthOptions)
|
||||||
usersAuthOptions.AllowEmailAuth = true
|
if err := h.app.Dao().SaveCollection(usersCollection); err != nil {
|
||||||
}
|
return err
|
||||||
usersCollection.SetOptions(usersAuthOptions)
|
}
|
||||||
if err := h.app.Dao().SaveCollection(usersCollection); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -159,6 +154,16 @@ func (h *Hub) Run() {
|
|||||||
// system creation defaults
|
// system creation defaults
|
||||||
h.app.OnModelBeforeCreate("systems").Add(func(e *core.ModelEvent) error {
|
h.app.OnModelBeforeCreate("systems").Add(func(e *core.ModelEvent) error {
|
||||||
record := e.Model.(*models.Record)
|
record := e.Model.(*models.Record)
|
||||||
|
if record.GetString("host") == "hubsys" {
|
||||||
|
// todo: check for hubsys existance and return error if exists (or make sure user is admin)
|
||||||
|
if record.GetString("name") == "x" {
|
||||||
|
hostname, _ := os.Hostname()
|
||||||
|
if hostname == "" {
|
||||||
|
hostname = "localhost"
|
||||||
|
}
|
||||||
|
record.Set("name", hostname)
|
||||||
|
}
|
||||||
|
}
|
||||||
record.Set("info", system.Info{})
|
record.Set("info", system.Info{})
|
||||||
record.Set("status", "pending")
|
record.Set("status", "pending")
|
||||||
return nil
|
return nil
|
||||||
@@ -246,6 +251,26 @@ func (h *Hub) updateSystems() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hub) updateSystem(record *models.Record) {
|
func (h *Hub) updateSystem(record *models.Record) {
|
||||||
|
switch record.GetString("host") {
|
||||||
|
case "hubsys":
|
||||||
|
h.updateHubSystem(record)
|
||||||
|
default:
|
||||||
|
h.updateRemoteSystem(record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update hub system stats with built-in agent
|
||||||
|
func (h *Hub) updateHubSystem(record *models.Record) {
|
||||||
|
if h.hubAgent == nil {
|
||||||
|
h.hubAgent = agent.NewAgent()
|
||||||
|
h.hubAgent.Run(nil, "")
|
||||||
|
}
|
||||||
|
systemData := h.hubAgent.GatherStats()
|
||||||
|
h.saveSystemStats(record, &systemData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to remote system and update system stats
|
||||||
|
func (h *Hub) updateRemoteSystem(record *models.Record) {
|
||||||
var client *ssh.Client
|
var client *ssh.Client
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -273,7 +298,7 @@ func (h *Hub) updateSystem(record *models.Record) {
|
|||||||
// if previous connection was closed, try again
|
// if previous connection was closed, try again
|
||||||
h.app.Logger().Error("Existing SSH connection closed. Retrying...", "host", record.GetString("host"), "port", record.GetString("port"))
|
h.app.Logger().Error("Existing SSH connection closed. Retrying...", "host", record.GetString("host"), "port", record.GetString("port"))
|
||||||
h.deleteSystemConnection(record)
|
h.deleteSystemConnection(record)
|
||||||
h.updateSystem(record)
|
h.updateRemoteSystem(record)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.app.Logger().Error("Failed to get system stats: ", "err", err.Error())
|
h.app.Logger().Error("Failed to get system stats: ", "err", err.Error())
|
||||||
@@ -281,6 +306,11 @@ func (h *Hub) updateSystem(record *models.Record) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// update system record
|
// update system record
|
||||||
|
h.saveSystemStats(record, &systemData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update system record with provided system.CombinedData
|
||||||
|
func (h *Hub) saveSystemStats(record *models.Record, systemData *system.CombinedData) {
|
||||||
record.Set("status", "up")
|
record.Set("status", "up")
|
||||||
record.Set("info", systemData.Info)
|
record.Set("info", systemData.Info)
|
||||||
if err := h.app.Dao().SaveRecord(record); err != nil {
|
if err := h.app.Dao().SaveRecord(record); err != nil {
|
||||||
@@ -320,14 +350,20 @@ func (h *Hub) updateSystemStatus(record *models.Record, status string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deletes the SSH connection (remote) or built-in agent reference
|
||||||
func (h *Hub) deleteSystemConnection(record *models.Record) {
|
func (h *Hub) deleteSystemConnection(record *models.Record) {
|
||||||
if _, ok := h.systemConnections[record.Id]; ok {
|
switch record.GetString("host") {
|
||||||
if h.systemConnections[record.Id] != nil {
|
case "hubsys":
|
||||||
h.systemConnections[record.Id].Close()
|
h.hubAgent = nil
|
||||||
|
default:
|
||||||
|
if _, ok := h.systemConnections[record.Id]; ok {
|
||||||
|
if h.systemConnections[record.Id] != nil {
|
||||||
|
h.systemConnections[record.Id].Close()
|
||||||
|
}
|
||||||
|
h.connectionLock.Lock()
|
||||||
|
defer h.connectionLock.Unlock()
|
||||||
|
delete(h.systemConnections, record.Id)
|
||||||
}
|
}
|
||||||
h.connectionLock.Lock()
|
|
||||||
defer h.connectionLock.Unlock()
|
|
||||||
delete(h.systemConnections, record.Id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ func (rm *RecordManager) AverageSystemStats(records []*models.Record) system.Sta
|
|||||||
sum.MemUsed += stats.MemUsed
|
sum.MemUsed += stats.MemUsed
|
||||||
sum.MemPct += stats.MemPct
|
sum.MemPct += stats.MemPct
|
||||||
sum.MemBuffCache += stats.MemBuffCache
|
sum.MemBuffCache += stats.MemBuffCache
|
||||||
|
sum.MemZfsArc += stats.MemZfsArc
|
||||||
sum.Swap += stats.Swap
|
sum.Swap += stats.Swap
|
||||||
sum.SwapUsed += stats.SwapUsed
|
sum.SwapUsed += stats.SwapUsed
|
||||||
sum.DiskTotal += stats.DiskTotal
|
sum.DiskTotal += stats.DiskTotal
|
||||||
@@ -200,6 +201,7 @@ func (rm *RecordManager) AverageSystemStats(records []*models.Record) system.Sta
|
|||||||
MemUsed: twoDecimals(sum.MemUsed / count),
|
MemUsed: twoDecimals(sum.MemUsed / count),
|
||||||
MemPct: twoDecimals(sum.MemPct / count),
|
MemPct: twoDecimals(sum.MemPct / count),
|
||||||
MemBuffCache: twoDecimals(sum.MemBuffCache / count),
|
MemBuffCache: twoDecimals(sum.MemBuffCache / count),
|
||||||
|
MemZfsArc: twoDecimals(sum.MemZfsArc / count),
|
||||||
Swap: twoDecimals(sum.Swap / count),
|
Swap: twoDecimals(sum.Swap / count),
|
||||||
SwapUsed: twoDecimals(sum.SwapUsed / count),
|
SwapUsed: twoDecimals(sum.SwapUsed / count),
|
||||||
DiskTotal: twoDecimals(sum.DiskTotal / count),
|
DiskTotal: twoDecimals(sum.DiskTotal / count),
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
|
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
|
||||||
|
|
||||||
import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'
|
import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'
|
||||||
import { useYAxisWidth, chartTimeData, cn, toFixedFloat, twoDecimalString } from '@/lib/utils'
|
import {
|
||||||
|
useYAxisWidth,
|
||||||
|
chartTimeData,
|
||||||
|
cn,
|
||||||
|
toFixedFloat,
|
||||||
|
twoDecimalString,
|
||||||
|
formatShortDate,
|
||||||
|
} from '@/lib/utils'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { $chartTime } from '@/lib/stores'
|
import { $chartTime } from '@/lib/stores'
|
||||||
@@ -72,6 +79,7 @@ export default function MemChart({
|
|||||||
<ChartTooltipContent
|
<ChartTooltipContent
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
itemSorter={(a, b) => a.order - b.order}
|
itemSorter={(a, b) => a.order - b.order}
|
||||||
|
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
|
||||||
contentFormatter={(item) => twoDecimalString(item.value) + ' GB'}
|
contentFormatter={(item) => twoDecimalString(item.value) + ' GB'}
|
||||||
indicator="line"
|
indicator="line"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -94,13 +94,15 @@ export function UserAuthForm({
|
|||||||
setErrors({ passwordConfirm: msg })
|
setErrors({ passwordConfirm: msg })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// create admin user
|
||||||
await pb.admins.create({
|
await pb.admins.create({
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
passwordConfirm: password,
|
passwordConfirm: password,
|
||||||
})
|
})
|
||||||
await pb.admins.authWithPassword(email, password)
|
await pb.admins.authWithPassword(email, password)
|
||||||
await pb.collection('users').create({
|
// create regular user
|
||||||
|
const user = await pb.collection('users').create({
|
||||||
username,
|
username,
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
@@ -108,6 +110,13 @@ export function UserAuthForm({
|
|||||||
role: 'admin',
|
role: 'admin',
|
||||||
verified: true,
|
verified: true,
|
||||||
})
|
})
|
||||||
|
// create hubsys
|
||||||
|
await pb.collection('systems').create({
|
||||||
|
name: 'x',
|
||||||
|
port: 'x',
|
||||||
|
host: 'hubsys',
|
||||||
|
users: user.id,
|
||||||
|
})
|
||||||
await pb.collection('users').authWithPassword(email, password)
|
await pb.collection('users').authWithPassword(email, password)
|
||||||
} else {
|
} else {
|
||||||
await pb.collection('users').authWithPassword(email, password)
|
await pb.collection('users').authWithPassword(email, password)
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
uptime = `${Math.trunc(system.info?.u / 86400)} days`
|
uptime = `${Math.trunc(system.info?.u / 86400)} days`
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
{ value: system.host, Icon: GlobeIcon },
|
{ value: system.host, Icon: GlobeIcon, hide: system.host === 'hubsys' },
|
||||||
{
|
{
|
||||||
value: system.info.h,
|
value: system.info.h,
|
||||||
Icon: MonitorIcon,
|
Icon: MonitorIcon,
|
||||||
|
|||||||
Reference in New Issue
Block a user