Full Syncthing tray wrapper with: - System tray with 5 icon states (idle/syncing/paused/error/disconnected) - Syncthing REST API client with auto-discovered API key - Long-polling event listener for real-time status - Transfer rate monitoring, folder tracking, recent files, conflict counting - Full context menu with folders, recent files, settings toggles - Embedded admin panel binary (webview, requires CGO) - OS notifications via beeep (per-event configurable) - Syncthing process management with auto-restart - Cross-platform installer with autostart - CI pipeline for Linux (.deb + .tar.gz) and Windows (.zip) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
87
internal/tray/tooltip.go
Normal file
87
internal/tray/tooltip.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package tray
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.davoryn.de/calic/syncwarden/internal/icons"
|
||||
"git.davoryn.de/calic/syncwarden/internal/monitor"
|
||||
)
|
||||
|
||||
// formatTooltip generates the tooltip text from aggregate status.
|
||||
func formatTooltip(s monitor.AggregateStatus, showRate bool) string {
|
||||
stateStr := stateLabel(s.State)
|
||||
|
||||
tip := fmt.Sprintf("SyncWarden: %s", stateStr)
|
||||
|
||||
// Devices
|
||||
tip += fmt.Sprintf(" | %d/%d devices", s.DevicesOnline, s.DevicesTotal)
|
||||
|
||||
// Transfer rate
|
||||
if showRate && (s.DownRate > 0 || s.UpRate > 0) {
|
||||
tip += fmt.Sprintf(" | ↓%s ↑%s", formatBytes(s.DownRate), formatBytes(s.UpRate))
|
||||
}
|
||||
|
||||
// Last sync
|
||||
if !s.LastSync.IsZero() {
|
||||
tip += fmt.Sprintf(" | Last sync: %s", formatTimeAgo(s.LastSync))
|
||||
}
|
||||
|
||||
return tip
|
||||
}
|
||||
|
||||
func stateLabel(s icons.State) string {
|
||||
switch s {
|
||||
case icons.StateIdle:
|
||||
return "Idle"
|
||||
case icons.StateSyncing:
|
||||
return "Syncing"
|
||||
case icons.StatePaused:
|
||||
return "Paused"
|
||||
case icons.StateError:
|
||||
return "Error"
|
||||
case icons.StateDisconnected:
|
||||
return "Disconnected"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func formatBytes(bps float64) string {
|
||||
if bps < 1024 {
|
||||
return fmt.Sprintf("%.0f B/s", bps)
|
||||
}
|
||||
if bps < 1024*1024 {
|
||||
return fmt.Sprintf("%.1f KB/s", bps/1024)
|
||||
}
|
||||
if bps < 1024*1024*1024 {
|
||||
return fmt.Sprintf("%.1f MB/s", bps/(1024*1024))
|
||||
}
|
||||
return fmt.Sprintf("%.1f GB/s", bps/(1024*1024*1024))
|
||||
}
|
||||
|
||||
func formatTimeAgo(t time.Time) string {
|
||||
d := time.Since(t)
|
||||
if d < time.Minute {
|
||||
return "just now"
|
||||
}
|
||||
if d < time.Hour {
|
||||
m := int(d.Minutes())
|
||||
if m == 1 {
|
||||
return "1 min ago"
|
||||
}
|
||||
return fmt.Sprintf("%d min ago", m)
|
||||
}
|
||||
if d < 24*time.Hour {
|
||||
h := int(d.Hours())
|
||||
if h == 1 {
|
||||
return "1 hour ago"
|
||||
}
|
||||
return fmt.Sprintf("%d hours ago", h)
|
||||
}
|
||||
days := int(d.Hours()) / 24
|
||||
if days == 1 {
|
||||
return "1 day ago"
|
||||
}
|
||||
return fmt.Sprintf("%d days ago", days)
|
||||
}
|
||||
Reference in New Issue
Block a user