Add server monitoring functionality, including server struct, metrics retrieval for CPU, memory, and disk usage, and status updates in the database.

This commit is contained in:
headlessdev 2025-04-20 22:45:54 +02:00
parent 0975bc5c5f
commit f548367e86
2 changed files with 183 additions and 5 deletions

View File

@ -28,6 +28,31 @@ type Application struct {
Online bool
}
type Server struct {
ID int
Name string
Monitoring bool
MonitoringURL sql.NullString
Online bool
CpuUsage sql.NullFloat64
RamUsage sql.NullFloat64
DiskUsage sql.NullFloat64
}
type CPUResponse struct {
Total float64 `json:"total"`
}
type MemoryResponse struct {
Percent float64 `json:"percent"`
}
type FSResponse []struct {
DeviceName string `json:"device_name"`
MntPoint string `json:"mnt_point"`
Percent float64 `json:"percent"`
}
type Notification struct {
ID int
Enabled bool
@ -108,20 +133,34 @@ func main() {
}
}()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
client := &http.Client{
appClient := &http.Client{
Timeout: 4 * time.Second,
}
// Server monitoring every 5 seconds
go func() {
serverClient := &http.Client{
Timeout: 5 * time.Second,
}
serverTicker := time.NewTicker(5 * time.Second)
defer serverTicker.Stop()
for range serverTicker.C {
servers := getServers(db)
checkAndUpdateServerStatus(db, serverClient, servers)
}
}()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for now := range ticker.C {
if now.Second()%10 != 0 {
continue
}
apps := getApplications(db)
checkAndUpdateStatus(db, client, apps)
checkAndUpdateStatus(db, appClient, apps)
}
}
@ -202,6 +241,32 @@ func getApplications(db *sql.DB) []Application {
return apps
}
func getServers(db *sql.DB) []Server {
rows, err := db.Query(
`SELECT id, name, monitoring, "monitoringURL", online, "cpuUsage", "ramUsage", "diskUsage"
FROM server WHERE monitoring = true`,
)
if err != nil {
fmt.Printf("Error fetching servers: %v\n", err)
return nil
}
defer rows.Close()
var servers []Server
for rows.Next() {
var server Server
if err := rows.Scan(
&server.ID, &server.Name, &server.Monitoring, &server.MonitoringURL,
&server.Online, &server.CpuUsage, &server.RamUsage, &server.DiskUsage,
); err != nil {
fmt.Printf("Error scanning server row: %v\n", err)
continue
}
servers = append(servers, server)
}
return servers
}
func checkAndUpdateStatus(db *sql.DB, client *http.Client, apps []Application) {
var notificationTemplate string
err := db.QueryRow("SELECT notification_text FROM settings LIMIT 1").Scan(&notificationTemplate)
@ -301,6 +366,114 @@ func checkAndUpdateStatus(db *sql.DB, client *http.Client, apps []Application) {
dbCancel2()
}
}
func checkAndUpdateServerStatus(db *sql.DB, client *http.Client, servers []Server) {
for _, server := range servers {
if !server.Monitoring || !server.MonitoringURL.Valid {
continue
}
logPrefix := fmt.Sprintf("[Server %s]", server.Name)
fmt.Printf("%s Checking...\n", logPrefix)
baseURL := strings.TrimSuffix(server.MonitoringURL.String, "/")
var cpuUsage, ramUsage, diskUsage float64
var online = true
// Get CPU usage
cpuResp, err := client.Get(fmt.Sprintf("%s/api/4/cpu", baseURL))
if err != nil {
fmt.Printf("%s CPU request failed: %v\n", logPrefix, err)
updateServerStatus(db, server.ID, false, 0, 0, 0)
continue
}
defer cpuResp.Body.Close()
if cpuResp.StatusCode != http.StatusOK {
fmt.Printf("%s Bad CPU status code: %d\n", logPrefix, cpuResp.StatusCode)
updateServerStatus(db, server.ID, false, 0, 0, 0)
continue
}
var cpuData CPUResponse
if err := json.NewDecoder(cpuResp.Body).Decode(&cpuData); err != nil {
fmt.Printf("%s Failed to parse CPU JSON: %v\n", logPrefix, err)
updateServerStatus(db, server.ID, false, 0, 0, 0)
continue
}
cpuUsage = cpuData.Total
// Get Memory usage
memResp, err := client.Get(fmt.Sprintf("%s/api/4/mem", baseURL))
if err != nil {
fmt.Printf("%s Memory request failed: %v\n", logPrefix, err)
updateServerStatus(db, server.ID, false, 0, 0, 0)
continue
}
defer memResp.Body.Close()
if memResp.StatusCode != http.StatusOK {
fmt.Printf("%s Bad memory status code: %d\n", logPrefix, memResp.StatusCode)
updateServerStatus(db, server.ID, false, 0, 0, 0)
continue
}
var memData MemoryResponse
if err := json.NewDecoder(memResp.Body).Decode(&memData); err != nil {
fmt.Printf("%s Failed to parse memory JSON: %v\n", logPrefix, err)
updateServerStatus(db, server.ID, false, 0, 0, 0)
continue
}
ramUsage = memData.Percent
// Get Disk usage
fsResp, err := client.Get(fmt.Sprintf("%s/api/4/fs", baseURL))
if err != nil {
fmt.Printf("%s Filesystem request failed: %v\n", logPrefix, err)
updateServerStatus(db, server.ID, false, 0, 0, 0)
continue
}
defer fsResp.Body.Close()
if fsResp.StatusCode != http.StatusOK {
fmt.Printf("%s Bad filesystem status code: %d\n", logPrefix, fsResp.StatusCode)
updateServerStatus(db, server.ID, false, 0, 0, 0)
continue
}
var fsData FSResponse
if err := json.NewDecoder(fsResp.Body).Decode(&fsData); err != nil {
fmt.Printf("%s Failed to parse filesystem JSON: %v\n", logPrefix, err)
updateServerStatus(db, server.ID, false, 0, 0, 0)
continue
}
// Use the first filesystem entry's percentage
if len(fsData) > 0 {
diskUsage = fsData[0].Percent
}
// Update server status with metrics
updateServerStatus(db, server.ID, online, cpuUsage, ramUsage, diskUsage)
fmt.Printf("%s Updated - CPU: %.2f%%, RAM: %.2f%%, Disk: %.2f%%\n",
logPrefix, cpuUsage, ramUsage, diskUsage)
}
}
func updateServerStatus(db *sql.DB, serverID int, online bool, cpuUsage, ramUsage, diskUsage float64) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := db.ExecContext(ctx,
`UPDATE server SET online = $1, "cpuUsage" = $2::float8, "ramUsage" = $3::float8, "diskUsage" = $4::float8
WHERE id = $5`,
online, cpuUsage, ramUsage, diskUsage, serverID,
)
if err != nil {
fmt.Printf("Failed to update server status (ID: %d): %v\n", serverID, err)
}
}
func sendNotifications(message string) {
notifMutex.RLock()
notifs := notifMutexCopy(notifications)

5
postcss.config.mjs Normal file
View File

@ -0,0 +1,5 @@
const config = {
plugins: ["@tailwindcss/postcss"],
};
export default config;