1 Commits

Author SHA1 Message Date
Zoe
0ba3b44af4 add betteruptime support
Some checks failed
Build and Push Docker Image to GHCR / build-and-push (push) Failing after 18m42s
2026-01-17 23:50:53 -06:00
4 changed files with 117 additions and 32 deletions

View File

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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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"