package httpapp import ( "fmt" "net/http" ) // DashboardData represents aggregated dashboard statistics type DashboardData struct { Storage struct { TotalCapacity uint64 `json:"total_capacity"` TotalAllocated uint64 `json:"total_allocated"` TotalAvailable uint64 `json:"total_available"` PoolCount int `json:"pool_count"` DatasetCount int `json:"dataset_count"` ZVOLCount int `json:"zvol_count"` SnapshotCount int `json:"snapshot_count"` } `json:"storage"` Services struct { SMBShares int `json:"smb_shares"` NFSExports int `json:"nfs_exports"` ISCSITargets int `json:"iscsi_targets"` SMBStatus bool `json:"smb_status"` NFSStatus bool `json:"nfs_status"` ISCSIStatus bool `json:"iscsi_status"` } `json:"services"` Jobs struct { Total int `json:"total"` Running int `json:"running"` Completed int `json:"completed"` Failed int `json:"failed"` } `json:"jobs"` RecentAuditLogs []map[string]interface{} `json:"recent_audit_logs,omitempty"` } // handleDashboardAPI returns aggregated dashboard data func (a *App) handleDashboardAPI(w http.ResponseWriter, r *http.Request) { data := DashboardData{} // Storage statistics pools, err := a.zfs.ListPools() if err == nil { data.Storage.PoolCount = len(pools) for _, pool := range pools { data.Storage.TotalCapacity += pool.Size data.Storage.TotalAllocated += pool.Allocated data.Storage.TotalAvailable += pool.Free } } datasets, err := a.zfs.ListDatasets("") if err == nil { data.Storage.DatasetCount = len(datasets) } zvols, err := a.zfs.ListZVOLs("") if err == nil { data.Storage.ZVOLCount = len(zvols) } snapshots, err := a.zfs.ListSnapshots("") if err == nil { data.Storage.SnapshotCount = len(snapshots) } // Service statistics smbShares := a.smbStore.List() data.Services.SMBShares = len(smbShares) nfsExports := a.nfsStore.List() data.Services.NFSExports = len(nfsExports) iscsiTargets := a.iscsiStore.List() data.Services.ISCSITargets = len(iscsiTargets) // Service status if a.smbService != nil { data.Services.SMBStatus, _ = a.smbService.GetStatus() } if a.nfsService != nil { data.Services.NFSStatus, _ = a.nfsService.GetStatus() } if a.iscsiService != nil { data.Services.ISCSIStatus, _ = a.iscsiService.GetStatus() } // Job statistics allJobs := a.jobManager.List("") data.Jobs.Total = len(allJobs) for _, job := range allJobs { switch job.Status { case "running": data.Jobs.Running++ case "completed": data.Jobs.Completed++ case "failed": data.Jobs.Failed++ } } // Recent audit logs (last 5) auditLogs := a.auditStore.List("", "", "", 5) data.RecentAuditLogs = make([]map[string]interface{}, 0, len(auditLogs)) for _, log := range auditLogs { data.RecentAuditLogs = append(data.RecentAuditLogs, map[string]interface{}{ "id": log.ID, "actor": log.Actor, "action": log.Action, "resource": log.Resource, "result": log.Result, "timestamp": log.Timestamp.Format("2006-01-02 15:04:05"), }) } writeJSON(w, http.StatusOK, data) } // formatBytes formats bytes to human-readable format func formatBytes(bytes uint64) string { const unit = 1024 if bytes < unit { return fmt.Sprintf("%d B", bytes) } div, exp := int64(unit), 0 for n := bytes / unit; n >= unit; n /= unit { div *= unit exp++ } return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) }