mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 01:39:34 +08:00
display longer records in charts
This commit is contained in:
11
main.go
11
main.go
@@ -83,12 +83,10 @@ func main() {
|
||||
})
|
||||
e.Router.GET("/icons/*", apis.StaticDirectoryHandler(os.DirFS("./site/public/icons"), false))
|
||||
e.Router.Any("/*", echo.WrapHandler(proxy))
|
||||
e.Router.Any("/", echo.WrapHandler(proxy))
|
||||
// e.Router.Any("/", echo.WrapHandler(proxy))
|
||||
default:
|
||||
assets, _ := site.Assets()
|
||||
icons, _ := site.Icons()
|
||||
e.Router.GET("/icons/*", apis.StaticDirectoryHandler(icons, false))
|
||||
e.Router.Any("/*", apis.StaticDirectoryHandler(assets, true))
|
||||
e.Router.GET("/icons/*", apis.StaticDirectoryHandler(site.Icons, false))
|
||||
e.Router.Any("/*", apis.StaticDirectoryHandler(site.Dist, true))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -186,8 +184,7 @@ func main() {
|
||||
})
|
||||
|
||||
app.OnModelAfterCreate("system_stats").Add(func(e *core.ModelEvent) error {
|
||||
createLongerRecords(e.Model.(*models.Record))
|
||||
// createLongerRecords(e.Model.(*models.Record).OriginalCopy(), e.Model.(*models.Record))
|
||||
createLongerRecords("system_stats", e.Model.(*models.Record))
|
||||
return nil
|
||||
})
|
||||
|
||||
|
@@ -97,11 +97,11 @@ Because Beszel is built on top of PocketBase, you can use the normal PocketBase
|
||||
|
||||
## Security
|
||||
|
||||
The hub and agent communicate over SSH, so they do not need to be exposed to the internet.
|
||||
The hub and agent communicate over SSH, so they don't need to be exposed to the internet. And the connection won't break if you put your own auth gateway, such as Authelia, in front of the hub.
|
||||
|
||||
When the hub is started for the first time, it generates an ED25519 key pair.
|
||||
|
||||
The agent's SSH server is configured to accept connections only using this key. It also does not provide a pty or accept any input, so it is not possible to execute commands on the agent.
|
||||
The agent's SSH server is configured to accept connections only using this key. It does not provide a pty or accept any input, so it is not possible to execute commands on the agent even if your private key is compromised.
|
||||
|
||||
## FAQ / Troubleshooting
|
||||
|
||||
|
108
records.go
108
records.go
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
func createLongerRecords(shorterRecord *models.Record) {
|
||||
func createLongerRecords(collectionName string, shorterRecord *models.Record) {
|
||||
shorterRecordType := shorterRecord.Get("type").(string)
|
||||
systemId := shorterRecord.Get("system").(string)
|
||||
// fmt.Println("create longer records", "recordType", shorterRecordType, "systemId", systemId)
|
||||
@@ -41,7 +42,7 @@ func createLongerRecords(shorterRecord *models.Record) {
|
||||
longerRecordPeriod := time.Now().UTC().Add(timeAgo + 10*time.Second).Format("2006-01-02 15:04:05")
|
||||
// check creation time of last 10m record
|
||||
lastLongerRecord, err := app.Dao().FindFirstRecordByFilter(
|
||||
"system_stats",
|
||||
collectionName,
|
||||
"type = {:type} && system = {:system} && created > {:created}",
|
||||
dbx.Params{"type": longerRecordType, "system": systemId, "created": longerRecordPeriod},
|
||||
)
|
||||
@@ -53,7 +54,7 @@ func createLongerRecords(shorterRecord *models.Record) {
|
||||
// get shorter records from the past x minutes
|
||||
// shorterRecordPeriod := time.Now().UTC().Add(timeAgo + time.Second).Format("2006-01-02 15:04:05")
|
||||
allShorterRecords, err := app.Dao().FindRecordsByFilter(
|
||||
"system_stats",
|
||||
collectionName,
|
||||
"type = {:type} && system = {:system} && created > {:created}",
|
||||
"-created",
|
||||
-1,
|
||||
@@ -67,7 +68,7 @@ func createLongerRecords(shorterRecord *models.Record) {
|
||||
}
|
||||
// average the shorter records and create 10m record
|
||||
averagedStats := averageSystemStats(allShorterRecords)
|
||||
collection, _ := app.Dao().FindCollectionByNameOrId("system_stats")
|
||||
collection, _ := app.Dao().FindCollectionByNameOrId(collectionName)
|
||||
tenMinRecord := models.NewRecord(collection)
|
||||
tenMinRecord.Set("system", systemId)
|
||||
tenMinRecord.Set("stats", averagedStats)
|
||||
@@ -78,65 +79,66 @@ func createLongerRecords(shorterRecord *models.Record) {
|
||||
|
||||
}
|
||||
|
||||
// func averageSystemStats(records []*models.Record) SystemStats {
|
||||
// numStats := len(records)
|
||||
// firstStats := records[0].Get("stats").(SystemStats)
|
||||
// sum := reflect.New(reflect.TypeOf(firstStats)).Elem()
|
||||
|
||||
// for _, record := range records {
|
||||
// stats := record.Get("stats").(SystemStats)
|
||||
// statValue := reflect.ValueOf(stats)
|
||||
// for i := 0; i < statValue.NumField(); i++ {
|
||||
// field := sum.Field(i)
|
||||
// field.SetFloat(field.Float() + statValue.Field(i).Float())
|
||||
// }
|
||||
// }
|
||||
|
||||
// average := reflect.New(reflect.TypeOf(firstStats)).Elem()
|
||||
// for i := 0; i < sum.NumField(); i++ {
|
||||
// average.Field(i).SetFloat(twoDecimals(sum.Field(i).Float() / float64(numStats)))
|
||||
// }
|
||||
|
||||
// return average.Interface().(SystemStats)
|
||||
// }
|
||||
|
||||
// calculate the average of a list of SystemStats using reflection
|
||||
func averageSystemStats(records []*models.Record) SystemStats {
|
||||
var sum SystemStats
|
||||
count := float64(len(records))
|
||||
sum := reflect.New(reflect.TypeOf(SystemStats{})).Elem()
|
||||
|
||||
for _, record := range records {
|
||||
var stats SystemStats
|
||||
json.Unmarshal([]byte(record.Get("stats").(types.JsonRaw)), &stats)
|
||||
sum.Cpu += stats.Cpu
|
||||
sum.Mem += stats.Mem
|
||||
sum.MemUsed += stats.MemUsed
|
||||
sum.MemPct += stats.MemPct
|
||||
sum.MemBuffCache += stats.MemBuffCache
|
||||
sum.Disk += stats.Disk
|
||||
sum.DiskUsed += stats.DiskUsed
|
||||
sum.DiskPct += stats.DiskPct
|
||||
sum.DiskRead += stats.DiskRead
|
||||
sum.DiskWrite += stats.DiskWrite
|
||||
sum.NetworkSent += stats.NetworkSent
|
||||
sum.NetworkRecv += stats.NetworkRecv
|
||||
statValue := reflect.ValueOf(stats)
|
||||
for i := 0; i < statValue.NumField(); i++ {
|
||||
field := sum.Field(i)
|
||||
field.SetFloat(field.Float() + statValue.Field(i).Float())
|
||||
}
|
||||
}
|
||||
|
||||
return SystemStats{
|
||||
Cpu: twoDecimals(sum.Cpu / count),
|
||||
Mem: twoDecimals(sum.Mem / count),
|
||||
MemUsed: twoDecimals(sum.MemUsed / count),
|
||||
MemPct: twoDecimals(sum.MemPct / count),
|
||||
MemBuffCache: twoDecimals(sum.MemBuffCache / count),
|
||||
Disk: twoDecimals(sum.Disk / count),
|
||||
DiskUsed: twoDecimals(sum.DiskUsed / count),
|
||||
DiskPct: twoDecimals(sum.DiskPct / count),
|
||||
DiskRead: twoDecimals(sum.DiskRead / count),
|
||||
DiskWrite: twoDecimals(sum.DiskWrite / count),
|
||||
NetworkSent: twoDecimals(sum.NetworkSent / count),
|
||||
NetworkRecv: twoDecimals(sum.NetworkRecv / count),
|
||||
average := reflect.New(reflect.TypeOf(SystemStats{})).Elem()
|
||||
for i := 0; i < sum.NumField(); i++ {
|
||||
average.Field(i).SetFloat(twoDecimals(sum.Field(i).Float() / count))
|
||||
}
|
||||
|
||||
return average.Interface().(SystemStats)
|
||||
}
|
||||
|
||||
// func averageSystemStats(records []*models.Record) SystemStats {
|
||||
// var sum SystemStats
|
||||
// count := float64(len(records))
|
||||
|
||||
// for _, record := range records {
|
||||
// var stats SystemStats
|
||||
// json.Unmarshal([]byte(record.Get("stats").(types.JsonRaw)), &stats)
|
||||
// sum.Cpu += stats.Cpu
|
||||
// sum.Mem += stats.Mem
|
||||
// sum.MemUsed += stats.MemUsed
|
||||
// sum.MemPct += stats.MemPct
|
||||
// sum.MemBuffCache += stats.MemBuffCache
|
||||
// sum.Disk += stats.Disk
|
||||
// sum.DiskUsed += stats.DiskUsed
|
||||
// sum.DiskPct += stats.DiskPct
|
||||
// sum.DiskRead += stats.DiskRead
|
||||
// sum.DiskWrite += stats.DiskWrite
|
||||
// sum.NetworkSent += stats.NetworkSent
|
||||
// sum.NetworkRecv += stats.NetworkRecv
|
||||
// }
|
||||
|
||||
// return SystemStats{
|
||||
// Cpu: twoDecimals(sum.Cpu / count),
|
||||
// Mem: twoDecimals(sum.Mem / count),
|
||||
// MemUsed: twoDecimals(sum.MemUsed / count),
|
||||
// MemPct: twoDecimals(sum.MemPct / count),
|
||||
// MemBuffCache: twoDecimals(sum.MemBuffCache / count),
|
||||
// Disk: twoDecimals(sum.Disk / count),
|
||||
// DiskUsed: twoDecimals(sum.DiskUsed / count),
|
||||
// DiskPct: twoDecimals(sum.DiskPct / count),
|
||||
// DiskRead: twoDecimals(sum.DiskRead / count),
|
||||
// DiskWrite: twoDecimals(sum.DiskWrite / count),
|
||||
// NetworkSent: twoDecimals(sum.NetworkSent / count),
|
||||
// NetworkRecv: twoDecimals(sum.NetworkRecv / count),
|
||||
// }
|
||||
// }
|
||||
|
||||
/* Round float to two decimals */
|
||||
func twoDecimals(value float64) float64 {
|
||||
return math.Round(value*100) / 100
|
||||
@@ -148,7 +150,7 @@ func deleteOldRecords(collection string, recordType string, timeLimit time.Durat
|
||||
timeLimitStamp := time.Now().UTC().Add(timeLimit).Format("2006-01-02 15:04:05")
|
||||
records, _ := app.Dao().FindRecordsByExpr(collection,
|
||||
dbx.NewExp("type = {:type}", dbx.Params{"type": recordType}),
|
||||
dbx.NewExp("created < {:created}", dbx.Params{"created": timeLimitStamp}),
|
||||
dbx.NewExp("created > {:created}", dbx.Params{"created": timeLimitStamp}),
|
||||
)
|
||||
for _, record := range records {
|
||||
if err := app.Dao().DeleteRecord(record); err != nil {
|
||||
|
@@ -1,17 +1,15 @@
|
||||
// Package site handles the Beszel frontend embedding.
|
||||
package site
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
)
|
||||
|
||||
//go:embed all:dist
|
||||
var assets embed.FS
|
||||
|
||||
func Assets() (fs.FS, error) {
|
||||
return fs.Sub(assets, "dist")
|
||||
}
|
||||
var Dist = echo.MustSubFS(assets, "dist")
|
||||
|
||||
func Icons() (fs.FS, error) {
|
||||
return fs.Sub(assets, "dist/icons")
|
||||
}
|
||||
var Icons = echo.MustSubFS(assets, "dist/icons")
|
||||
|
@@ -73,12 +73,16 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
|
||||
// get stats
|
||||
useEffect(() => {
|
||||
if (!server.id) {
|
||||
if (!server.id || !chartTime) {
|
||||
return
|
||||
}
|
||||
pb.collection<SystemStatsRecord>('system_stats')
|
||||
.getFullList({
|
||||
filter: `system="${server.id}" && created > "${getPbTimestamp('1h')}"`,
|
||||
filter: pb.filter('system={:id} && created > {:created} && type={:type}', {
|
||||
id: server.id,
|
||||
created: getPbTimestamp(chartTime),
|
||||
type: chartTimeData[chartTime].type,
|
||||
}),
|
||||
fields: 'created,stats',
|
||||
sort: 'created',
|
||||
})
|
||||
@@ -86,7 +90,7 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
// console.log('sctats', records)
|
||||
setServerStats(records)
|
||||
})
|
||||
}, [server])
|
||||
}, [server, chartTime])
|
||||
|
||||
useEffect(() => {
|
||||
if (updatedSystem.id === server.id) {
|
||||
|
@@ -123,26 +123,31 @@ export function getPbTimestamp(timeString: ChartTimes) {
|
||||
|
||||
export const chartTimeData = {
|
||||
'1h': {
|
||||
type: '1m',
|
||||
label: '1 hour',
|
||||
format: (timestamp: string) => hourWithMinutes(timestamp),
|
||||
getOffset: (endTime: Date) => timeHour.offset(endTime, -1),
|
||||
},
|
||||
'12h': {
|
||||
type: '10m',
|
||||
label: '12 hours',
|
||||
format: (timestamp: string) => hourWithMinutes(timestamp),
|
||||
getOffset: (endTime: Date) => timeHour.offset(endTime, -12),
|
||||
},
|
||||
'24h': {
|
||||
type: '20m',
|
||||
label: '24 hours',
|
||||
format: (timestamp: string) => hourWithMinutes(timestamp),
|
||||
getOffset: (endTime: Date) => timeHour.offset(endTime, -24),
|
||||
},
|
||||
'1w': {
|
||||
type: '120m',
|
||||
label: '1 week',
|
||||
format: (timestamp: string) => formatDay(timestamp),
|
||||
getOffset: (endTime: Date) => timeDay.offset(endTime, -7),
|
||||
},
|
||||
'30d': {
|
||||
type: '480m',
|
||||
label: '30 days',
|
||||
format: (timestamp: string) => formatDay(timestamp),
|
||||
getOffset: (endTime: Date) => timeDay.offset(endTime, -30),
|
||||
|
Reference in New Issue
Block a user