package components import ( "context" "fmt" "github.com/starfleetcptn/gomft/internal/db" "os/exec" "strconv" "strings" ) type DashboardData struct { RecentJobs []db.JobHistory ActiveTransfers int CompletedToday int FailedTransfers int Configs map[uint]db.TransferConfig RcloneVersion string LatestVersion string CurrentVersion string } // GetRcloneVersion executes the rclone --version command and returns the version string func GetRcloneVersion() string { cmd := exec.Command("rclone", "--version") output, err := cmd.Output() if err != nil { return "Unknown" } // Parse the version from output (typically first line contains "rclone v1.XX.X") outputStr := string(output) lines := strings.Split(outputStr, "\n") if len(lines) > 0 { parts := strings.Split(lines[0], " ") for _, part := range parts { if strings.HasPrefix(part, "v") { return part } } } return "Unknown" } // isNewerVersionAvailable checks if the latest version is newer than the current version func isNewerVersionAvailable(current, latest string) bool { // If either version is empty, we can't do a comparison if current == "" || latest == "" { return false } // Special case for DEV versions - always show update available if current == "DEV" { return true } // Strip 'v' prefix if present for comparison if strings.HasPrefix(current, "v") { current = current[1:] } if strings.HasPrefix(latest, "v") { latest = latest[1:] } // Split versions into components currentParts := strings.Split(current, ".") latestParts := strings.Split(latest, ".") // Handle non-semver format in either version if len(currentParts) < 2 || len(latestParts) < 2 { // If format doesn't match semver pattern, do string comparison return current != latest && latest != "" } // Compare major, minor, patch versions for i := 0; i < len(currentParts) && i < len(latestParts); i++ { // Parse to integers currentNum, err1 := strconv.Atoi(currentParts[i]) latestNum, err2 := strconv.Atoi(latestParts[i]) // If either can't be parsed, do string comparison if err1 != nil || err2 != nil { if currentParts[i] < latestParts[i] { return true } else if currentParts[i] > latestParts[i] { return false } continue } // Compare numbers if latestNum > currentNum { return true } else if latestNum < currentNum { return false } } // If all components are equal but latest has more components, it's newer return len(latestParts) > len(currentParts) } templ Dashboard(ctx context.Context, data DashboardData) { @LayoutWithContext("Dashboard", ctx) {

Dashboard

Active Transfers

{ strconv.Itoa(data.ActiveTransfers) }
Currently in progress

Completed Today

{ strconv.Itoa(data.CompletedToday) }
Successfully completed

Failed Transfers

{ strconv.Itoa(data.FailedTransfers) }
Require attention

Recent Jobs

if len(data.RecentJobs) == 0 {

No recent jobs found

Get started by creating your first job.

Create your first job
} else {
    for _, job := range data.RecentJobs {
  • if job.Status == "completed" { } else if job.Status == "failed" { } else { }

    { getConfigNameForHistory(job, data.Configs) }

    Started: { job.StartTime.Format("Jan 02, 2006 15:04:05") }

    View
  • }
}

Quick Actions

Create New Config Create New Job View Transfer History

System Status

Server Online
Scheduler Running
Database Connected
Rclone if data.RcloneVersion != "" { { data.RcloneVersion } } else { Unknown }
Application Version if data.CurrentVersion != "" && data.LatestVersion != "" && isNewerVersionAvailable(data.CurrentVersion, data.LatestVersion) {
{ data.CurrentVersion } { data.LatestVersion } Available
} else { if data.CurrentVersion != "" { { data.CurrentVersion } } else { Up to date } }
} }