package web import ( "bufio" "net/http" "os" "sort" "strings" "syscall" "github.com/darroyo/nasctl/internal/system" ) type diskUsage struct { Path string `json:"path"` TotalBytes uint64 `json:"total_bytes"` FreeBytes uint64 `json:"free_bytes"` UsedBytes uint64 `json:"used_bytes"` UsedPercent float64 `json:"used_percent"` Source string `json:"source"` UsedBy []string `json:"used_by,omitempty"` Available bool `json:"available"` Error string `json:"error,omitempty"` } type serviceStatus struct { Name string `json:"name"` Active bool `json:"active"` State string `json:"state"` } func (s *Server) handleSystemStatus(w http.ResponseWriter, r *http.Request) { status := map[string]any{ "disks": s.collectDiskUsage(), "services": s.collectServiceStatus(r), } writeJSON(w, http.StatusOK, status) } func (s *Server) collectDiskUsage() []diskUsage { entries := make(map[string]*diskUsage) entries["/"] = &diskUsage{Path: "/", Source: "system", Available: true} shares, err := s.DB.ListSambaShares() if err == nil { for _, share := range shares { key := share.Path if e, ok := entries[key]; ok { e.UsedBy = append(e.UsedBy, share.Name) } else { entries[key] = &diskUsage{Path: key, Source: "samba", UsedBy: []string{share.Name}, Available: true} } } } exports, err := s.DB.ListNFSExports() if err == nil { for _, exp := range exports { key := exp.Path label := "nfs:" + exp.Path if e, ok := entries[key]; ok { e.UsedBy = append(e.UsedBy, label) } else { entries[key] = &diskUsage{Path: key, Source: "nfs", UsedBy: []string{label}, Available: true} } } } readProcMounts(entries) var result []diskUsage for _, e := range entries { fillDiskUsage(e) result = append(result, *e) } sort.Slice(result, func(i, j int) bool { if result[i].Source != result[j].Source { return result[i].Source < result[j].Source } return result[i].Path < result[j].Path }) return result } func readProcMounts(entries map[string]*diskUsage) { f, err := os.Open("/proc/mounts") if err != nil { return } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { fields := strings.Fields(scanner.Text()) if len(fields) < 3 { continue } mountPoint := fields[1] if _, ok := entries[mountPoint]; !ok { entries[mountPoint] = &diskUsage{Path: mountPoint, Source: "mount", Available: true} } } } func fillDiskUsage(e *diskUsage) { var stat syscall.Statfs_t if err := syscall.Statfs(e.Path, &stat); err != nil { e.Available = false e.Error = "path no disponible" return } e.Available = true e.TotalBytes = stat.Blocks * uint64(stat.Bsize) e.FreeBytes = stat.Bavail * uint64(stat.Bsize) e.UsedBytes = e.TotalBytes - e.FreeBytes if e.TotalBytes > 0 { e.UsedPercent = float64(e.UsedBytes) / float64(e.TotalBytes) * 100 } } func (s *Server) collectServiceStatus(r *http.Request) []serviceStatus { services := []string{"smbd", "nfs-server"} var statuses []serviceStatus for _, name := range services { stdout, _, err := system.Run(r.Context(), "systemctl", "is-active", name) state := strings.TrimSpace(stdout) if state == "" && err != nil { state = "unknown" } statuses = append(statuses, serviceStatus{ Name: name, Active: state == "active", State: state, }) } return statuses }