add uptime robot

This commit is contained in:
Zoe
2025-03-28 07:43:52 -05:00
parent 0c983e7c27
commit 4fc22910b4
3 changed files with 157 additions and 10 deletions

View File

@@ -19,10 +19,11 @@ Passport is a simple, fast, and lightweight web dashboard/new tab replacement.
1. Clone the repository 1. Clone the repository
2. Configure the `.env` file, an example is provided in the `.env example` file 2. Configure the `.env` file, an example is provided in the `.env example` file
- The `OPENWEATHER_API_KEY` is required for the weather data to be displayed - The `OPENWEATHER_API_KEY` is required for the weather data to be displayed, if you want to disable the weather data, set `PASSPORT_ENABLE_WEATHER` to `false`
- The `OPENWEATHER_LAT` and `OPENWEATHER_LON` are required for the weather data to be displayed - The `OPENWEATHER_LAT` and `OPENWEATHER_LON` are required for the weather data to be displayed
- The `PASSPORT_ADMIN_USERNAME` and `PASSPORT_ADMIN_PASSWORD` are required for the admin dashboard - The `PASSPORT_ADMIN_USERNAME` and `PASSPORT_ADMIN_PASSWORD` are required for the admin dashboard
- The `PASSPORT_SEARCH_PROVIDER` is the search provider used for the search bar, %s is replaced with the search query - The `PASSPORT_SEARCH_PROVIDER` is the search provider used for the search bar, %s is replaced with the search query
- The `UPTIMEROBOT_API_KEY` is required for the uptime data to be displayed, if you want to disable the uptime data, set `PASSPORT_ENABLE_UPTIME` to `false`
3. Run `zqdgr build` to build a standalone binary 3. Run `zqdgr build` to build a standalone binary
4. Deploy `passport` to your web server 4. Deploy `passport` to your web server
5. profit 5. profit

137
main.go
View File

@@ -70,6 +70,8 @@ var (
type App struct { type App struct {
*WeatherCache *WeatherCache
*CategoryManager *CategoryManager
*UptimeManager
db *sql.DB
} }
func NewApp(dbPath string) (*App, error) { func NewApp(dbPath string) (*App, error) {
@@ -93,12 +95,115 @@ func NewApp(dbPath string) (*App, error) {
return nil, err return nil, err
} }
var weatherCache *WeatherCache
if os.Getenv("PASSPORT_ENABLE_WEATHER") != "false" {
weatherCache = NewWeatherCache()
}
var uptimeManager *UptimeManager
if os.Getenv("PASSPORT_ENABLE_UPTIME") != "false" {
uptimeManager = NewUptimeManager()
}
return &App{ return &App{
WeatherCache: NewWeatherCache(), WeatherCache: weatherCache,
CategoryManager: categoryManager, CategoryManager: categoryManager,
UptimeManager: uptimeManager,
}, nil }, nil
} }
type UptimeRobotSite struct {
FriendlyName string `json:"friendly_name"`
Url string `json:"url"`
Status int `json:"status"`
}
type UptimeManager struct {
sites []UptimeRobotSite
lastUpdate time.Time
mutex sync.RWMutex
updateChan chan struct{}
updateInterval int
apiKey string
}
func NewUptimeManager() *UptimeManager {
if os.Getenv("UPTIMEROBOT_API_KEY") == "" {
log.Fatalln("UptimeRobot API Key is required!")
return nil
}
updateInterval, err := strconv.Atoi(os.Getenv("UPTIMEROBOT_UPDATE_INTERVAL"))
if err != nil || updateInterval < 1 {
updateInterval = 5
}
uptimeManager := &UptimeManager{
updateChan: make(chan struct{}),
updateInterval: updateInterval,
apiKey: os.Getenv("UPTIMEROBOT_API_KEY"),
sites: []UptimeRobotSite{},
}
go uptimeManager.updateWorker()
uptimeManager.updateChan <- struct{}{}
return uptimeManager
}
func (u *UptimeManager) getUptime() []UptimeRobotSite {
u.mutex.RLock()
defer u.mutex.RUnlock()
return u.sites
}
func (u *UptimeManager) updateWorker() {
ticker := time.NewTicker(time.Duration(u.updateInterval) * time.Minute)
defer ticker.Stop()
for {
select {
case <-u.updateChan:
u.update()
case <-ticker.C:
u.update()
}
}
}
type UptimeRobotResponse struct {
Monitors []UptimeRobotSite `json:"monitors"`
}
func (u *UptimeManager) update() {
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
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading response: %v\n", err)
return
}
var monitors UptimeRobotResponse
if err := json.Unmarshal(body, &monitors); err != nil {
fmt.Printf("Error parsing uptime data: %v\n", err)
return
}
fmt.Printf("%+v", monitors.Monitors)
u.mutex.Lock()
u.sites = monitors.Monitors
u.lastUpdate = time.Now()
u.mutex.Unlock()
}
type OpenWeatherResponse struct { type OpenWeatherResponse struct {
Weather []struct { Weather []struct {
Name string `json:"main"` Name string `json:"main"`
@@ -533,6 +638,10 @@ func main() {
return "" return ""
}) })
engine.AddFunc("eq", func(a, b any) bool {
return a == b
})
router := fiber.New(fiber.Config{ router := fiber.New(fiber.Config{
Views: engine, Views: engine,
}) })
@@ -550,17 +659,27 @@ func main() {
})) }))
router.Get("/", func(c fiber.Ctx) error { router.Get("/", func(c fiber.Ctx) error {
renderData := fiber.Map{
"SearchProvider": os.Getenv("PASSPORT_SEARCH_PROVIDER"),
"Categories": app.CategoryManager.Categories,
}
if os.Getenv("PASSPORT_ENABLE_WEATHER") != "false" {
weather := app.WeatherCache.GetWeather() weather := app.WeatherCache.GetWeather()
return c.Render("views/index", fiber.Map{ renderData["WeatherData"] = fiber.Map{
"SearchProvider": os.Getenv("PASSPORT_SEARCH_PROVIDER"),
"WeatherData": fiber.Map{
"Temp": weather.Temperature, "Temp": weather.Temperature,
"Desc": weather.WeatherText, "Desc": weather.WeatherText,
"Icon": getWeatherIcon(weather.Icon), "Icon": getWeatherIcon(weather.Icon),
}, }
"Categories": app.CategoryManager.Categories, }
}, "layouts/main")
if os.Getenv("PASSPORT_ENABLE_UPTIME") != "false" {
fmt.Printf("%+v", app.UptimeManager.getUptime())
renderData["UptimeData"] = app.UptimeManager.getUptime()
}
return c.Render("views/index", renderData, "layouts/main")
}) })
router.Use(middleware.AdminMiddleware(app.db)) router.Use(middleware.AdminMiddleware(app.db))
@@ -825,5 +944,7 @@ func main() {
}) })
} }
router.Listen(":3000") router.Listen(":3000", fiber.ListenConfig{
EnablePrefork: os.Getenv("PASSPORT_ENABLE_PREFORK") == "true",
})
} }

View File

@@ -1,4 +1,5 @@
<main class="flex justify-center items-center h-screen relative bg-[#0E0A0E]"> <main class="flex justify-center items-center h-screen relative bg-[#0E0A0E]">
{{#if WeatherData}}
<div class="absolute top-2.5 left-2.5"> <div class="absolute top-2.5 left-2.5">
<div class="text-[#BABABA] flex items-center"> <div class="text-[#BABABA] flex items-center">
<span class="mr-2 flex items-center"> <span class="mr-2 flex items-center">
@@ -10,6 +11,30 @@
</div> </div>
</div> </div>
</div> </div>
{{/if}}
{{#if UptimeData}}
<div class="absolute top-2.5 right-2.5">
<div class="text-[#BABABA] flex items-end flex-col">
{{!-- loop over UptimeData --}}
{{#each UptimeData}}
<div class="flex items-center">
<span class="mr-2 flex items-center">
{{{this.FriendlyName}}}
</span>
<div class="relative my-auto">
{{#if (eq this.Status 2)}}
<span class="absolute w-2 h-2 rounded-full bg-emerald-400 animate-ping block"></span>
<span class="relative w-2 h-2 rounded-full bg-emerald-500 block"></span>
{{else}}
<span class="absolute w-2 h-2 rounded-full bg-rose-400 animate-ping block"></span>
<span class="relative w-2 h-2 rounded-full bg-rose-500 block"></span>
{{/if}}
</div>
</div>
{{/each}}
</div>
</div>
{{/if}}
<div class="flex flex-col items-center w-full mx-6"> <div class="flex flex-col items-center w-full mx-6">
<div class="flex items-center pb-2.5"> <div class="flex items-center pb-2.5">
<svg class="mr-3 aspect-square w-[clamp(48px,10vw,60px)]" viewBox="0 0 100 100" fill="none" <svg class="mr-3 aspect-square w-[clamp(48px,10vw,60px)]" viewBox="0 0 100 100" fill="none"