add uptime robot
This commit is contained in:
@@ -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
|
||||||
|
|||||||
139
main.go
139
main.go
@@ -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 {
|
||||||
weather := app.WeatherCache.GetWeather()
|
renderData := fiber.Map{
|
||||||
|
|
||||||
return c.Render("views/index", fiber.Map{
|
|
||||||
"SearchProvider": os.Getenv("PASSPORT_SEARCH_PROVIDER"),
|
"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,
|
"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",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user