diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index cbbaa52..b93dac6 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -10,8 +10,6 @@ on: jobs: build-and-push: - env: - RUNNER_TOOL_CACHE: /toolcache runs-on: ubuntu-latest permissions: diff --git a/Dockerfile b/Dockerfile index 6a26787..1d93d1a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ RUN apt update && apt install -y upx unzip RUN curl -fsSL https://bun.com/install | BUN_INSTALL=/usr bash -RUN go install github.com/juls0730/zqdgr@latest +RUN go install github.com/juls0730/zqdgr@v0.0.6-1 WORKDIR /app diff --git a/README.md b/README.md index 964afc0..553bd04 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ Passport is a simple, fast, and lightweight web dashboard/new tab replacement. ### Prerequisites -- [ZQDGR](https://github.com/juls0730/zqdgr) -- [Go](https://go.dev/doc/install) -- [sqlite3](https://www.sqlite.org/download.html) -- [TailwdinCSS CLI](https://github.com/tailwindlabs/tailwindcss/releases/latest) +- [ZQDGR](https://github.com/juls0730/zqdgr) +- [Go](https://go.dev/doc/install) +- [sqlite3](https://www.sqlite.org/download.html) +- [TailwdinCSS CLI](https://github.com/tailwindlabs/tailwindcss/releases/latest) ## Usage @@ -85,10 +85,11 @@ The weather integration is optional, and will be enabled automatically if you pr The uptime integration is optional, and will be enabled automatically if you provide an API key. The following only applies if you are using the UptimeRobot integration. -| Environment Variable | Description | Required | Default | -| ------------------------- | ------------------------------------------------- | -------- | ------- | -| `PASSPORT_UPTIME_API_KEY` | The UptimeRobot API key | true | | -| `UPTIME_UPDATE_INTERVAL` | The interval in seconds to update the uptime data | false | 300 | +| Environment Variable | Description | Required | Default | +| ------------------------- | ------------------------------------------------------------------ | -------- | ----------- | +| `UPTIME_PROVIDER` | The uptime provider to use, either `uptimerobot` or `betteruptime` | false | uptimerobot | +| `PASSPORT_UPTIME_API_KEY` | The UptimeRobot API key | true | | +| `UPTIME_UPDATE_INTERVAL` | The interval in seconds to update the uptime data | false | 300 | ### Adding links and categories diff --git a/src/services/uptimeService.go b/src/services/uptimeService.go index 81d06a9..b75d15a 100644 --- a/src/services/uptimeService.go +++ b/src/services/uptimeService.go @@ -6,6 +6,7 @@ import ( "io" "log" "net/http" + "sort" "sync" "time" ) @@ -16,19 +17,20 @@ type DepricatedUptimeConfig struct { } type UptimeConfig struct { + Provider string `env:"UPTIME_PROVIDER" envDefault:"uptimerobot"` APIKey string UpdateInterval int `env:"UPTIME_UPDATE_INTERVAL" envDefault:"300"` } -type UptimeRobotSite struct { - FriendlyName string `json:"friendly_name"` - Url string `json:"url"` - Status int `json:"status"` - Up bool `json:"-"` +type UptimeSite struct { + FriendlyName string + Url string + Up bool } type UptimeManager struct { - sites []UptimeRobotSite + provider string + sites []UptimeSite lastUpdate time.Time mutex sync.RWMutex updateChan chan struct{} @@ -38,7 +40,7 @@ type UptimeManager struct { func NewUptimeManager(config *UptimeConfig) *UptimeManager { if config.APIKey == "" { - log.Fatalln("UptimeRobot API Key is required!") + log.Fatalln("An API Key is required to use Uptime Monitoring!") return nil } @@ -48,10 +50,11 @@ func NewUptimeManager(config *UptimeConfig) *UptimeManager { } uptimeManager := &UptimeManager{ + provider: config.Provider, updateChan: make(chan struct{}), updateInterval: updateInterval, apiKey: config.APIKey, - sites: []UptimeRobotSite{}, + sites: []UptimeSite{}, } go uptimeManager.updateWorker() @@ -61,7 +64,7 @@ func NewUptimeManager(config *UptimeConfig) *UptimeManager { return uptimeManager } -func (u *UptimeManager) GetUptime() []UptimeRobotSite { +func (u *UptimeManager) GetUptime() []UptimeSite { u.mutex.RLock() defer u.mutex.RUnlock() return u.sites @@ -81,36 +84,119 @@ func (u *UptimeManager) updateWorker() { } } +type UptimeRobotSite struct { + FriendlyName string `json:"friendly_name"` + Url string `json:"url"` + Status int `json:"status"` +} + type UptimeRobotResponse struct { Monitors []UptimeRobotSite `json:"monitors"` } +type BetterUptimeSite struct { + MonitorType string `json:"type"` + Attributes struct { + PronounceableName string `json:"pronounceable_name"` + Url string `json:"url"` + Status string `json:"status"` + } `json:"attributes"` +} + +type BetterUptimeResponse struct { + Monitors []BetterUptimeSite `json:"data"` +} + func (u *UptimeManager) update() { + var monitors []UptimeSite + switch u.provider { + case "uptimerobot": + monitors = u.updateUptimeRobot() + case "betteruptime": + monitors = u.updateBetterUptime() + default: + log.Fatalln("Invalid Uptime Provider!") + } + + u.mutex.Lock() + u.sites = monitors + u.lastUpdate = time.Now() + u.mutex.Unlock() +} + +func (u *UptimeManager) updateUptimeRobot() []UptimeSite { resp, err := http.Post("https://api.uptimerobot.com/v2/getMonitors?api_key="+u.apiKey, "application/json", nil) if err != nil { fmt.Printf("Error fetching uptime data: %v\n", err) - return + return []UptimeSite{} } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { fmt.Printf("Error reading response: %v\n", err) - return + return []UptimeSite{} } - var monitors UptimeRobotResponse - if err := json.Unmarshal(body, &monitors); err != nil { + var rawMonitors UptimeRobotResponse + if err := json.Unmarshal(body, &rawMonitors); err != nil { fmt.Printf("Error parsing uptime data: %v\n", err) - return + return []UptimeSite{} } - for i, monitor := range monitors.Monitors { - monitors.Monitors[i].Up = monitor.Status == 2 + var monitors []UptimeSite + for _, rawMonitor := range rawMonitors.Monitors { + monitors = append(monitors, UptimeSite{ + FriendlyName: rawMonitor.FriendlyName, + Url: rawMonitor.Url, + Up: rawMonitor.Status == 2, + }) } - u.mutex.Lock() - u.sites = monitors.Monitors - u.lastUpdate = time.Now() - u.mutex.Unlock() + return monitors +} + +func (u *UptimeManager) updateBetterUptime() []UptimeSite { + client := &http.Client{} + req, err := http.NewRequest("GET", "https://uptime.betterstack.com/api/v2/monitors", nil) + if err != nil { + fmt.Printf("Error fetching uptime data: %v\n", err) + return []UptimeSite{} + } + req.Header.Add("Authorization", "Bearer "+u.apiKey) + + resp, err := client.Do(req) + if err != nil { + fmt.Printf("Error fetching uptime data: %v\n", err) + return []UptimeSite{} + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Printf("Error reading response: %v\n", err) + return []UptimeSite{} + } + + var rawMonitors BetterUptimeResponse + if err := json.Unmarshal(body, &rawMonitors); err != nil { + fmt.Printf("Error parsing uptime data: %v\n", err) + return []UptimeSite{} + } + + // alphabetically sort the monitors because UptimeRobot does, but BetterUptime doesnt (or sorts by something else?), and I want them to be consistent + sort.Slice(rawMonitors.Monitors, func(i, j int) bool { + return rawMonitors.Monitors[i].Attributes.PronounceableName < rawMonitors.Monitors[j].Attributes.PronounceableName + }) + + var monitors []UptimeSite + for _, rawMonitor := range rawMonitors.Monitors { + monitors = append(monitors, UptimeSite{ + FriendlyName: rawMonitor.Attributes.PronounceableName, + Url: rawMonitor.Attributes.Url, + Up: rawMonitor.Attributes.Status == "up", + }) + } + + return monitors } diff --git a/zqdgr.config.json b/zqdgr.config.json index ba3229d..33eb5e2 100644 --- a/zqdgr.config.json +++ b/zqdgr.config.json @@ -1,6 +1,6 @@ { "name": "passport", - "version": "0.3.4", + "version": "0.3.5", "description": "Passport is a simple, lightweight, and fast dashboard/new tab page for your browser.", "author": "juls0730", "license": "BSL-1.0", @@ -18,7 +18,7 @@ "build:arm64": "zqdgr generate && GOOS=linux GOARCH=arm64 go build -tags netgo,prod -ldflags=\"-w -s\" -o passport-linux-arm64 src/main.go && upx passport-linux-arm64" }, "pattern": "src/**/*.{go,hbs,css,js,svg,png,jpg,jpeg,webp,woff2,ico,webp}", - "excluded_files": [ + "excluded_globs": [ "src/assets/styles" ], "shutdown_signal": "SIGINT"