add betteruptime support
Some checks failed
Build and Push Docker Image to GHCR / build-and-push (push) Failing after 18m42s

This commit is contained in:
Zoe
2026-01-17 23:50:07 -06:00
parent 3ffd439f88
commit 0ba3b44af4
4 changed files with 117 additions and 32 deletions

View File

@@ -10,8 +10,6 @@ on:
jobs: jobs:
build-and-push: build-and-push:
env:
RUNNER_TOOL_CACHE: /toolcache
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:

View File

@@ -10,10 +10,10 @@ Passport is a simple, fast, and lightweight web dashboard/new tab replacement.
### Prerequisites ### Prerequisites
- [ZQDGR](https://github.com/juls0730/zqdgr) - [ZQDGR](https://github.com/juls0730/zqdgr)
- [Go](https://go.dev/doc/install) - [Go](https://go.dev/doc/install)
- [sqlite3](https://www.sqlite.org/download.html) - [sqlite3](https://www.sqlite.org/download.html)
- [TailwdinCSS CLI](https://github.com/tailwindlabs/tailwindcss/releases/latest) - [TailwdinCSS CLI](https://github.com/tailwindlabs/tailwindcss/releases/latest)
## Usage ## 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. 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 | | Environment Variable | Description | Required | Default |
| ------------------------- | ------------------------------------------------- | -------- | ------- | | ------------------------- | ------------------------------------------------------------------ | -------- | ----------- |
| `PASSPORT_UPTIME_API_KEY` | The UptimeRobot API key | true | | | `UPTIME_PROVIDER` | The uptime provider to use, either `uptimerobot` or `betteruptime` | false | uptimerobot |
| `UPTIME_UPDATE_INTERVAL` | The interval in seconds to update the uptime data | false | 300 | | `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 ### Adding links and categories

View File

@@ -6,6 +6,7 @@ import (
"io" "io"
"log" "log"
"net/http" "net/http"
"sort"
"sync" "sync"
"time" "time"
) )
@@ -16,19 +17,20 @@ type DepricatedUptimeConfig struct {
} }
type UptimeConfig struct { type UptimeConfig struct {
Provider string `env:"UPTIME_PROVIDER" envDefault:"uptimerobot"`
APIKey string APIKey string
UpdateInterval int `env:"UPTIME_UPDATE_INTERVAL" envDefault:"300"` UpdateInterval int `env:"UPTIME_UPDATE_INTERVAL" envDefault:"300"`
} }
type UptimeRobotSite struct { type UptimeSite struct {
FriendlyName string `json:"friendly_name"` FriendlyName string
Url string `json:"url"` Url string
Status int `json:"status"` Up bool
Up bool `json:"-"`
} }
type UptimeManager struct { type UptimeManager struct {
sites []UptimeRobotSite provider string
sites []UptimeSite
lastUpdate time.Time lastUpdate time.Time
mutex sync.RWMutex mutex sync.RWMutex
updateChan chan struct{} updateChan chan struct{}
@@ -38,7 +40,7 @@ type UptimeManager struct {
func NewUptimeManager(config *UptimeConfig) *UptimeManager { func NewUptimeManager(config *UptimeConfig) *UptimeManager {
if config.APIKey == "" { if config.APIKey == "" {
log.Fatalln("UptimeRobot API Key is required!") log.Fatalln("An API Key is required to use Uptime Monitoring!")
return nil return nil
} }
@@ -48,10 +50,11 @@ func NewUptimeManager(config *UptimeConfig) *UptimeManager {
} }
uptimeManager := &UptimeManager{ uptimeManager := &UptimeManager{
provider: config.Provider,
updateChan: make(chan struct{}), updateChan: make(chan struct{}),
updateInterval: updateInterval, updateInterval: updateInterval,
apiKey: config.APIKey, apiKey: config.APIKey,
sites: []UptimeRobotSite{}, sites: []UptimeSite{},
} }
go uptimeManager.updateWorker() go uptimeManager.updateWorker()
@@ -61,7 +64,7 @@ func NewUptimeManager(config *UptimeConfig) *UptimeManager {
return uptimeManager return uptimeManager
} }
func (u *UptimeManager) GetUptime() []UptimeRobotSite { func (u *UptimeManager) GetUptime() []UptimeSite {
u.mutex.RLock() u.mutex.RLock()
defer u.mutex.RUnlock() defer u.mutex.RUnlock()
return u.sites 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 { type UptimeRobotResponse struct {
Monitors []UptimeRobotSite `json:"monitors"` 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() { 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) resp, err := http.Post("https://api.uptimerobot.com/v2/getMonitors?api_key="+u.apiKey, "application/json", nil)
if err != nil { if err != nil {
fmt.Printf("Error fetching uptime data: %v\n", err) fmt.Printf("Error fetching uptime data: %v\n", err)
return return []UptimeSite{}
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
fmt.Printf("Error reading response: %v\n", err) fmt.Printf("Error reading response: %v\n", err)
return return []UptimeSite{}
} }
var monitors UptimeRobotResponse var rawMonitors UptimeRobotResponse
if err := json.Unmarshal(body, &monitors); err != nil { if err := json.Unmarshal(body, &rawMonitors); err != nil {
fmt.Printf("Error parsing uptime data: %v\n", err) fmt.Printf("Error parsing uptime data: %v\n", err)
return return []UptimeSite{}
} }
for i, monitor := range monitors.Monitors { var monitors []UptimeSite
monitors.Monitors[i].Up = monitor.Status == 2 for _, rawMonitor := range rawMonitors.Monitors {
monitors = append(monitors, UptimeSite{
FriendlyName: rawMonitor.FriendlyName,
Url: rawMonitor.Url,
Up: rawMonitor.Status == 2,
})
} }
u.mutex.Lock() return monitors
u.sites = monitors.Monitors }
u.lastUpdate = time.Now()
u.mutex.Unlock() 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
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "passport", "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.", "description": "Passport is a simple, lightweight, and fast dashboard/new tab page for your browser.",
"author": "juls0730", "author": "juls0730",
"license": "BSL-1.0", "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" "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}", "pattern": "src/**/*.{go,hbs,css,js,svg,png,jpg,jpeg,webp,woff2,ico,webp}",
"excluded_files": [ "excluded_globs": [
"src/assets/styles" "src/assets/styles"
], ],
"shutdown_signal": "SIGINT" "shutdown_signal": "SIGINT"