300 lines
7.7 KiB
Go
300 lines
7.7 KiB
Go
package httpapp
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"runtime"
|
|
"time"
|
|
)
|
|
|
|
// SystemInfo represents system diagnostic information
|
|
type SystemInfo struct {
|
|
Version string `json:"version"`
|
|
Uptime string `json:"uptime"`
|
|
GoVersion string `json:"go_version"`
|
|
NumGoroutine int `json:"num_goroutines"`
|
|
Memory MemoryInfo `json:"memory"`
|
|
Services map[string]ServiceInfo `json:"services"`
|
|
Database DatabaseInfo `json:"database,omitempty"`
|
|
}
|
|
|
|
// MemoryInfo represents memory statistics
|
|
type MemoryInfo struct {
|
|
Alloc uint64 `json:"alloc"` // bytes allocated
|
|
TotalAlloc uint64 `json:"total_alloc"` // bytes allocated (cumulative)
|
|
Sys uint64 `json:"sys"` // bytes obtained from system
|
|
NumGC uint32 `json:"num_gc"` // number of GC cycles
|
|
}
|
|
|
|
// ServiceInfo represents service status
|
|
type ServiceInfo struct {
|
|
Status string `json:"status"` // "running", "stopped", "error"
|
|
LastCheck string `json:"last_check"` // timestamp
|
|
Message string `json:"message,omitempty"`
|
|
}
|
|
|
|
// DatabaseInfo represents database connection info
|
|
type DatabaseInfo struct {
|
|
Connected bool `json:"connected"`
|
|
Path string `json:"path,omitempty"`
|
|
}
|
|
|
|
// handleSystemInfo returns system diagnostic information
|
|
func (a *App) handleSystemInfo(w http.ResponseWriter, r *http.Request) {
|
|
var m runtime.MemStats
|
|
runtime.ReadMemStats(&m)
|
|
|
|
uptime := time.Since(a.startTime)
|
|
|
|
info := SystemInfo{
|
|
Version: "v0.1.0-dev",
|
|
Uptime: fmt.Sprintf("%.0f seconds", uptime.Seconds()),
|
|
GoVersion: runtime.Version(),
|
|
NumGoroutine: runtime.NumGoroutine(),
|
|
Memory: MemoryInfo{
|
|
Alloc: m.Alloc,
|
|
TotalAlloc: m.TotalAlloc,
|
|
Sys: m.Sys,
|
|
NumGC: m.NumGC,
|
|
},
|
|
Services: make(map[string]ServiceInfo),
|
|
}
|
|
|
|
// Check service statuses
|
|
smbStatus, smbErr := a.smbService.GetStatus()
|
|
if smbErr == nil {
|
|
status := "stopped"
|
|
if smbStatus {
|
|
status = "running"
|
|
}
|
|
info.Services["smb"] = ServiceInfo{
|
|
Status: status,
|
|
LastCheck: time.Now().Format(time.RFC3339),
|
|
}
|
|
} else {
|
|
info.Services["smb"] = ServiceInfo{
|
|
Status: "error",
|
|
LastCheck: time.Now().Format(time.RFC3339),
|
|
Message: smbErr.Error(),
|
|
}
|
|
}
|
|
|
|
nfsStatus, nfsErr := a.nfsService.GetStatus()
|
|
if nfsErr == nil {
|
|
status := "stopped"
|
|
if nfsStatus {
|
|
status = "running"
|
|
}
|
|
info.Services["nfs"] = ServiceInfo{
|
|
Status: status,
|
|
LastCheck: time.Now().Format(time.RFC3339),
|
|
}
|
|
} else {
|
|
info.Services["nfs"] = ServiceInfo{
|
|
Status: "error",
|
|
LastCheck: time.Now().Format(time.RFC3339),
|
|
Message: nfsErr.Error(),
|
|
}
|
|
}
|
|
|
|
iscsiStatus, iscsiErr := a.iscsiService.GetStatus()
|
|
if iscsiErr == nil {
|
|
status := "stopped"
|
|
if iscsiStatus {
|
|
status = "running"
|
|
}
|
|
info.Services["iscsi"] = ServiceInfo{
|
|
Status: status,
|
|
LastCheck: time.Now().Format(time.RFC3339),
|
|
}
|
|
} else {
|
|
info.Services["iscsi"] = ServiceInfo{
|
|
Status: "error",
|
|
LastCheck: time.Now().Format(time.RFC3339),
|
|
Message: iscsiErr.Error(),
|
|
}
|
|
}
|
|
|
|
// Database info
|
|
if a.database != nil {
|
|
info.Database = DatabaseInfo{
|
|
Connected: true,
|
|
Path: a.cfg.DatabaseConn,
|
|
}
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, info)
|
|
}
|
|
|
|
// handleHealthCheck provides detailed health check information
|
|
func (a *App) handleHealthCheck(w http.ResponseWriter, r *http.Request) {
|
|
type HealthStatus struct {
|
|
Status string `json:"status"` // "healthy", "degraded", "unhealthy"
|
|
Timestamp string `json:"timestamp"`
|
|
Checks map[string]string `json:"checks"`
|
|
}
|
|
|
|
health := HealthStatus{
|
|
Status: "healthy",
|
|
Timestamp: time.Now().Format(time.RFC3339),
|
|
Checks: make(map[string]string),
|
|
}
|
|
|
|
// Check ZFS service
|
|
if a.zfs != nil {
|
|
_, err := a.zfs.ListPools()
|
|
if err != nil {
|
|
health.Checks["zfs"] = "unhealthy: " + err.Error()
|
|
health.Status = "degraded"
|
|
} else {
|
|
health.Checks["zfs"] = "healthy"
|
|
}
|
|
} else {
|
|
health.Checks["zfs"] = "unhealthy: service not initialized"
|
|
health.Status = "unhealthy"
|
|
}
|
|
|
|
// Check database
|
|
if a.database != nil {
|
|
// Try a simple query to check database health
|
|
if err := a.database.DB.Ping(); err != nil {
|
|
health.Checks["database"] = "unhealthy: " + err.Error()
|
|
health.Status = "degraded"
|
|
} else {
|
|
health.Checks["database"] = "healthy"
|
|
}
|
|
} else {
|
|
health.Checks["database"] = "not configured"
|
|
}
|
|
|
|
// Check services
|
|
smbStatus, smbErr := a.smbService.GetStatus()
|
|
if smbErr != nil {
|
|
health.Checks["smb"] = "unhealthy: " + smbErr.Error()
|
|
health.Status = "degraded"
|
|
} else if !smbStatus {
|
|
health.Checks["smb"] = "stopped"
|
|
} else {
|
|
health.Checks["smb"] = "healthy"
|
|
}
|
|
|
|
nfsStatus, nfsErr := a.nfsService.GetStatus()
|
|
if nfsErr != nil {
|
|
health.Checks["nfs"] = "unhealthy: " + nfsErr.Error()
|
|
health.Status = "degraded"
|
|
} else if !nfsStatus {
|
|
health.Checks["nfs"] = "stopped"
|
|
} else {
|
|
health.Checks["nfs"] = "healthy"
|
|
}
|
|
|
|
iscsiStatus, iscsiErr := a.iscsiService.GetStatus()
|
|
if iscsiErr != nil {
|
|
health.Checks["iscsi"] = "unhealthy: " + iscsiErr.Error()
|
|
health.Status = "degraded"
|
|
} else if !iscsiStatus {
|
|
health.Checks["iscsi"] = "stopped"
|
|
} else {
|
|
health.Checks["iscsi"] = "healthy"
|
|
}
|
|
|
|
// Check maintenance mode
|
|
if a.maintenanceService != nil && a.maintenanceService.IsEnabled() {
|
|
health.Checks["maintenance"] = "enabled"
|
|
if health.Status == "healthy" {
|
|
health.Status = "maintenance"
|
|
}
|
|
} else {
|
|
health.Checks["maintenance"] = "disabled"
|
|
}
|
|
|
|
// Set HTTP status based on health
|
|
statusCode := http.StatusOK
|
|
if health.Status == "unhealthy" {
|
|
statusCode = http.StatusServiceUnavailable
|
|
} else if health.Status == "degraded" {
|
|
statusCode = http.StatusOK // Still OK, but with warnings
|
|
}
|
|
|
|
w.WriteHeader(statusCode)
|
|
writeJSON(w, statusCode, health)
|
|
}
|
|
|
|
// handleLogs returns recent log entries (if available)
|
|
func (a *App) handleLogs(w http.ResponseWriter, r *http.Request) {
|
|
// For now, return audit logs as system logs
|
|
// In a full implementation, this would return application logs
|
|
limit := 100
|
|
if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
|
|
fmt.Sscanf(limitStr, "%d", &limit)
|
|
if limit > 1000 {
|
|
limit = 1000
|
|
}
|
|
if limit < 1 {
|
|
limit = 1
|
|
}
|
|
}
|
|
|
|
// Get recent audit logs
|
|
logs := a.auditStore.List("", "", "", limit)
|
|
|
|
type LogEntry struct {
|
|
Timestamp string `json:"timestamp"`
|
|
Level string `json:"level"`
|
|
Actor string `json:"actor"`
|
|
Action string `json:"action"`
|
|
Resource string `json:"resource"`
|
|
Result string `json:"result"`
|
|
Message string `json:"message,omitempty"`
|
|
IP string `json:"ip,omitempty"`
|
|
}
|
|
|
|
entries := make([]LogEntry, 0, len(logs))
|
|
for _, log := range logs {
|
|
level := "INFO"
|
|
if log.Result == "failure" {
|
|
level = "ERROR"
|
|
}
|
|
|
|
entries = append(entries, LogEntry{
|
|
Timestamp: log.Timestamp.Format(time.RFC3339),
|
|
Level: level,
|
|
Actor: log.Actor,
|
|
Action: log.Action,
|
|
Resource: log.Resource,
|
|
Result: log.Result,
|
|
Message: log.Message,
|
|
IP: log.IP,
|
|
})
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"logs": entries,
|
|
"count": len(entries),
|
|
})
|
|
}
|
|
|
|
// handleGC triggers a garbage collection and returns stats
|
|
func (a *App) handleGC(w http.ResponseWriter, r *http.Request) {
|
|
var before, after runtime.MemStats
|
|
runtime.ReadMemStats(&before)
|
|
runtime.GC()
|
|
runtime.ReadMemStats(&after)
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"before": map[string]interface{}{
|
|
"alloc": before.Alloc,
|
|
"total_alloc": before.TotalAlloc,
|
|
"sys": before.Sys,
|
|
"num_gc": before.NumGC,
|
|
},
|
|
"after": map[string]interface{}{
|
|
"alloc": after.Alloc,
|
|
"total_alloc": after.TotalAlloc,
|
|
"sys": after.Sys,
|
|
"num_gc": after.NumGC,
|
|
},
|
|
"freed": before.Alloc - after.Alloc,
|
|
})
|
|
}
|