Features: - Multi-platform agents (Linux, macOS, Windows - AMD64 & ARM64) - Real-time CPU, RAM, and disk usage monitoring - Responsive web dashboard with live status indicators - Session-based authentication with secure credentials - Stale agent detection and removal (6+ months inactive) - Auto-refresh dashboard (5 second intervals) - 15-second agent reporting intervals - Auto-generated agent IDs from hostnames - In-memory storage (zero database setup) - Minimal dependencies (Chi router + Templ templating) Project Structure: - cmd/: Agent and Server executables - internal/: API, Auth, Stats, Storage, and UI packages - views/: Templ templates for dashboard UI - Makefile: Build automation for all platforms Ready for deployment with comprehensive documentation: - README.md: Full project documentation - QUICKSTART.md: Getting started guide - AGENTS.md: Development guidelines
139 lines
3.8 KiB
Plaintext
139 lines
3.8 KiB
Plaintext
package views
|
|
|
|
import "fmt"
|
|
import "time"
|
|
|
|
// FormatBytes converts bytes to human-readable format
|
|
func FormatBytes(bytes uint64) string {
|
|
const (
|
|
kb = 1024
|
|
mb = kb * 1024
|
|
gb = mb * 1024
|
|
tb = gb * 1024
|
|
)
|
|
|
|
switch {
|
|
case bytes >= tb:
|
|
return fmt.Sprintf("%.2f TB", float64(bytes)/float64(tb))
|
|
case bytes >= gb:
|
|
return fmt.Sprintf("%.2f GB", float64(bytes)/float64(gb))
|
|
case bytes >= mb:
|
|
return fmt.Sprintf("%.2f MB", float64(bytes)/float64(mb))
|
|
case bytes >= kb:
|
|
return fmt.Sprintf("%.2f KB", float64(bytes)/float64(kb))
|
|
default:
|
|
return fmt.Sprintf("%d B", bytes)
|
|
}
|
|
}
|
|
|
|
// FormatPercent formats percentage with 1 decimal place
|
|
func FormatPercent(percent float64) string {
|
|
return fmt.Sprintf("%.1f%%", percent)
|
|
}
|
|
|
|
// FormatTime formats time as relative or absolute
|
|
func FormatTime(t time.Time) string {
|
|
if t.IsZero() {
|
|
return "Never"
|
|
}
|
|
|
|
duration := time.Since(t)
|
|
switch {
|
|
case duration < time.Minute:
|
|
return "Just now"
|
|
case duration < time.Hour:
|
|
return fmt.Sprintf("%d minutes ago", int(duration.Minutes()))
|
|
case duration < 24*time.Hour:
|
|
return fmt.Sprintf("%d hours ago", int(duration.Hours()))
|
|
case duration < 30*24*time.Hour:
|
|
return fmt.Sprintf("%d days ago", int(duration.Hours()/24))
|
|
default:
|
|
return fmt.Sprintf("%d months ago", int(duration.Hours()/24/30))
|
|
}
|
|
}
|
|
|
|
// StatusColor returns CSS class for status based on conditions
|
|
func StatusColor(cpuUsage, ramPercent, lastSeenAge time.Duration) string {
|
|
if lastSeenAge > 24*time.Hour {
|
|
return "status-red"
|
|
}
|
|
if cpuUsage > 80 || ramPercent > 80 {
|
|
return "status-yellow"
|
|
}
|
|
return "status-green"
|
|
}
|
|
|
|
// IsAgentOnline checks if agent is currently online (reported within 15 seconds)
|
|
func IsAgentOnline(lastSeen time.Time) bool {
|
|
return time.Since(lastSeen) < 15*time.Second
|
|
}
|
|
|
|
templ StatusCard(label string, value string, status string) {
|
|
<div class="card">
|
|
<div class="card-title">
|
|
{ label }
|
|
<span class={ "status-badge", status }>{ status }</span>
|
|
</div>
|
|
<div class="metric-value" style="font-size: 1.5rem; margin-top: 0.5rem;">
|
|
{ value }
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
templ UsageBar(label string, used uint64, total uint64) {
|
|
<div class="metric-row">
|
|
<span class="metric-label">{ label }</span>
|
|
<span class="metric-value">{ FormatBytes(used) } / { FormatBytes(total) }</span>
|
|
</div>
|
|
<div class="progress-bar">
|
|
if total > 0 {
|
|
<div class={ "progress-fill", calcProgressClass(float64(used)/float64(total)) } style={ fmt.Sprintf("width: %.1f%%", float64(used)/float64(total)*100) }></div>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
// calcProgressClass returns the CSS class based on percentage
|
|
func calcProgressClass(percent float64) string {
|
|
if percent > 0.8 {
|
|
return "progress-high"
|
|
}
|
|
if percent > 0.6 {
|
|
return "progress-medium"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
templ AgentRow(id string, hostname string, cpuUsage float64, ramUsage uint64, ramTotal uint64, diskUsage uint64, diskTotal uint64, lastSeen time.Time) {
|
|
<tr>
|
|
<td>
|
|
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
|
@AgentStatusBadge(lastSeen)
|
|
<a href={ templ.SafeURL(fmt.Sprintf("/agents/%s", id)) }>{ hostname }</a>
|
|
</div>
|
|
</td>
|
|
<td>{ FormatPercent(cpuUsage) }</td>
|
|
<td>{ FormatBytes(ramUsage) } / { FormatBytes(ramTotal) }</td>
|
|
<td>{ FormatBytes(diskUsage) } / { FormatBytes(diskTotal) }</td>
|
|
<td class="timestamp">{ FormatTime(lastSeen) }</td>
|
|
</tr>
|
|
}
|
|
|
|
templ AgentStatusBadge(lastSeen time.Time) {
|
|
if IsAgentOnline(lastSeen) {
|
|
<span class="status-badge status-green" style="margin: 0;">Online</span>
|
|
} else {
|
|
<span class="status-badge status-red" style="margin: 0;">Offline</span>
|
|
}
|
|
}
|
|
|
|
templ StaleAgentAlert(count int, agents []interface{}) {
|
|
if count > 0 {
|
|
<div class="alert alert-warning">
|
|
<strong>⚠️ { count } agent(s) haven't reported for 6+ months</strong>
|
|
<p style="margin-top: 0.5rem; font-size: 0.875rem;">
|
|
These agents may be offline. You can remove them to keep your dashboard clean.
|
|
</p>
|
|
</div>
|
|
}
|
|
}
|