diff --git a/README.md b/README.md index 2b80c96..d0b77e0 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,11 @@ Passport is a simple, fast, and lightweight web dashboard/new tab replacement. 1. Clone the repository 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 `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 `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 4. Deploy `passport` to your web server 5. profit diff --git a/main.go b/main.go index 2dc54db..b1ce438 100644 --- a/main.go +++ b/main.go @@ -70,6 +70,8 @@ var ( type App struct { *WeatherCache *CategoryManager + *UptimeManager + db *sql.DB } func NewApp(dbPath string) (*App, error) { @@ -93,12 +95,115 @@ func NewApp(dbPath string) (*App, error) { 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{ - WeatherCache: NewWeatherCache(), + WeatherCache: weatherCache, CategoryManager: categoryManager, + UptimeManager: uptimeManager, }, 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 { Weather []struct { Name string `json:"main"` @@ -533,6 +638,10 @@ func main() { return "" }) + engine.AddFunc("eq", func(a, b any) bool { + return a == b + }) + router := fiber.New(fiber.Config{ Views: engine, }) @@ -550,17 +659,27 @@ func main() { })) router.Get("/", func(c fiber.Ctx) error { - weather := app.WeatherCache.GetWeather() - - return c.Render("views/index", fiber.Map{ + renderData := fiber.Map{ "SearchProvider": os.Getenv("PASSPORT_SEARCH_PROVIDER"), - "WeatherData": fiber.Map{ + "Categories": app.CategoryManager.Categories, + } + + if os.Getenv("PASSPORT_ENABLE_WEATHER") != "false" { + weather := app.WeatherCache.GetWeather() + + renderData["WeatherData"] = fiber.Map{ "Temp": weather.Temperature, "Desc": weather.WeatherText, "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)) @@ -825,5 +944,7 @@ func main() { }) } - router.Listen(":3000") + router.Listen(":3000", fiber.ListenConfig{ + EnablePrefork: os.Getenv("PASSPORT_ENABLE_PREFORK") == "true", + }) } diff --git a/templates/views/index.hbs b/templates/views/index.hbs index 3ace5d9..f980f2c 100644 --- a/templates/views/index.hbs +++ b/templates/views/index.hbs @@ -1,4 +1,5 @@
+ {{#if WeatherData}}
@@ -10,6 +11,30 @@
+ {{/if}} + {{#if UptimeData}} +
+
+ {{!-- loop over UptimeData --}} + {{#each UptimeData}} +
+ + {{{this.FriendlyName}}} + +
+ {{#if (eq this.Status 2)}} + + + {{else}} + + + {{/if}} +
+
+ {{/each}} +
+
+ {{/if}}