diff options
| author | Xe Iaso <me@xeiaso.net> | 2024-08-21 14:10:15 -0400 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2024-08-21 14:20:44 -0400 |
| commit | 39660d978304ebce08a9200f4ef63c0013cab7ac (patch) | |
| tree | 68f1b7f77e1690d5e7021cb221f454dff146905f /cmd | |
| parent | de3aa62dbc608ec1fbcf676275340644ed1fd031 (diff) | |
| download | x-39660d978304ebce08a9200f4ef63c0013cab7ac.tar.xz x-39660d978304ebce08a9200f4ef63c0013cab7ac.zip | |
cmd/hdrwtch: closer to implementation
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/hdrwtch/dao.go | 42 | ||||
| -rw-r--r-- | cmd/hdrwtch/execute.go | 47 | ||||
| -rw-r--r-- | cmd/hdrwtch/main.go | 45 | ||||
| -rw-r--r-- | cmd/hdrwtch/probes.go | 61 | ||||
| -rw-r--r-- | cmd/hdrwtch/probes.templ | 120 | ||||
| -rw-r--r-- | cmd/hdrwtch/probes_templ.go | 251 | ||||
| -rw-r--r-- | cmd/hdrwtch/static/css/styles.css | 3 | ||||
| -rw-r--r-- | cmd/hdrwtch/static/img/logo.svg | 9 | ||||
| -rw-r--r-- | cmd/hdrwtch/styles.css | 6 | ||||
| -rw-r--r-- | cmd/hdrwtch/telegram.go | 6 | ||||
| -rw-r--r-- | cmd/hdrwtch/web.templ | 23 | ||||
| -rw-r--r-- | cmd/hdrwtch/web_templ.go | 75 | ||||
| -rw-r--r-- | cmd/xedn/main.go | 2 |
13 files changed, 490 insertions, 200 deletions
diff --git a/cmd/hdrwtch/dao.go b/cmd/hdrwtch/dao.go index 8693cf6..a651e21 100644 --- a/cmd/hdrwtch/dao.go +++ b/cmd/hdrwtch/dao.go @@ -9,6 +9,7 @@ import ( "github.com/ncruces/go-sqlite3/gormlite" slogGorm "github.com/orandin/slog-gorm" "gorm.io/gorm" + "gorm.io/gorm/clause" gormPrometheus "gorm.io/plugin/prometheus" ) @@ -46,6 +47,47 @@ func New(dbLoc string) (*DAO, error) { return &DAO{db: db}, nil } +func (dao *DAO) UpsertUser(ctx context.Context, user *TelegramUser) error { + if err := dao.db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "id"}}, // primary key + DoUpdates: clause.AssignmentColumns([]string{"first_name", "last_name", "photo_url", "auth_date"}), // column needed to be updated + }). + Create(user). + WithContext(ctx). + Error; err != nil { + return fmt.Errorf("failed to create user: %w", err) + } + + return nil +} + +func (dao *DAO) CreateProbe(ctx context.Context, probe *Probe, tu *TelegramUser) error { + // Check if user has reached probe limit + var count int64 + if err := dao.db.Model(&Probe{}).Where("user_id = ?", tu.ID).Count(&count).Error; err != nil { + return fmt.Errorf("failed to count probes: %w", err) + } + + if count >= int64(tu.ProbeLimit) { + return fmt.Errorf("probe limit reached") + } + + if err := dao.db.Create(probe).WithContext(ctx).Error; err != nil { + return fmt.Errorf("failed to create probe: %w", err) + } + + return nil +} + +func (dao *DAO) CountProbes(ctx context.Context, userID string) (int64, error) { + var count int64 + if err := dao.db.Model(&Probe{}).Where("user_id = ?", userID).Count(&count).WithContext(ctx).Error; err != nil { + return 0, fmt.Errorf("failed to count probes: %w", err) + } + + return count, nil +} + func (dao *DAO) GetProbe(ctx context.Context, id string, userID string) (*Probe, error) { var probe Probe diff --git a/cmd/hdrwtch/execute.go b/cmd/hdrwtch/execute.go new file mode 100644 index 0000000..9cefd51 --- /dev/null +++ b/cmd/hdrwtch/execute.go @@ -0,0 +1,47 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "time" + + "within.website/x/web/useragent" +) + +func checkURL(ctx context.Context, probe Probe) (*ProbeResult, error) { + result := &ProbeResult{ + ProbeID: probe.ID, + Region: *region, + } + + userAgent := useragent.GenUserAgent("hdrwtch/1.0", fmt.Sprintf("https://%s/docs/why-in-logs", *domain)) + + start := time.Now() + req, err := http.NewRequestWithContext(ctx, "GET", probe.URL, nil) + if err != nil { + result.Success = false + result.Remark = fmt.Sprintf("failed to create request: %v", err) + return result, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("User-Agent", userAgent) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + result.Success = false + result.Remark = fmt.Sprintf("failed to execute request: %v", err) + return result, fmt.Errorf("failed to execute request: %w", err) + } + defer resp.Body.Close() + + result.Success = true + result.StatusCode = resp.StatusCode + result.Duration = time.Since(start) + + return &ProbeResult{ + ProbeID: probe.ID, + StatusCode: resp.StatusCode, + Duration: time.Since(start), + }, nil +} diff --git a/cmd/hdrwtch/main.go b/cmd/hdrwtch/main.go index 9ee4bc9..b982725 100644 --- a/cmd/hdrwtch/main.go +++ b/cmd/hdrwtch/main.go @@ -8,11 +8,11 @@ import ( "log/slog" "net/http" "os" + "time" "github.com/a-h/templ" "github.com/gorilla/securecookie" "github.com/gorilla/sessions" - "gorm.io/gorm/clause" "within.website/x/htmx" "within.website/x/internal" ) @@ -26,7 +26,9 @@ var ( cookieSecret = flag.String("cookie-secret", "", "Secret key for cookie store") dbURL = flag.String("database-url", "", "Database URL") dbLoc = flag.String("database-loc", "./var/hdrwtch.db", "Database location") + domain = flag.String("domain", "shiroko-wsl.shark-harmonic.ts.net", "Domain to use for user agent") port = flag.String("port", "8080", "Port to listen on") + region = flag.String("fly-region", "yow-dev", "Region of this instance") //go:embed static staticFS embed.FS @@ -55,7 +57,7 @@ func main() { htmx.Mount(mux) - mux.Handle("/static/", http.FileServer(http.FS(staticFS))) + mux.Handle("/static/", internal.UnchangingCache(http.FileServer(http.FS(staticFS)))) mux.Handle("/{$}", templ.Handler(base("Home", nil, anonNavBar(true), homePage()))) mux.HandleFunc("/login", s.loginHandler) @@ -73,11 +75,25 @@ func main() { mux.Handle("PUT /probe/{id}", s.loggedIn(s.probeUpdate)) mux.Handle("DELETE /probe/{id}", s.loggedIn(s.probeDelete)) - mux.Handle("/", templ.Handler( - base("Not Found", nil, anonNavBar(true), notFoundPage()), - templ.WithStatus(http.StatusNotFound), + mux.Handle("/", internal.UnchangingCache( + templ.Handler( + base("Not Found", nil, anonNavBar(true), notFoundPage()), + templ.WithStatus(http.StatusNotFound), + ), )) + // test routes + mux.HandleFunc("GET /test/curr", func(w http.ResponseWriter, r *http.Request) { + val := time.Now().Format(http.TimeFormat) + w.Header().Set("Last-Modified", val) + fmt.Fprintln(w, val) + }) + mux.HandleFunc("GET /test/constant", func(w http.ResponseWriter, r *http.Request) { + val := "Mon, 02 Jan 2006 15:04:05 GMT" + w.Header().Set("Last-Modified", val) + fmt.Fprintln(w, val) + }) + slog.Info("listening", "on", "http://localhost:"+*port) log.Fatal(http.ListenAndServe(":"+*port, mux)) @@ -126,11 +142,7 @@ func (s *Server) loginCallbackHandler(w http.ResponseWriter, r *http.Request) { return } - if err := s.dao.db.Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "id"}}, // primary key - DoUpdates: clause.AssignmentColumns([]string{"first_name", "last_name", "photo_url", "auth_date"}), // column needed to be updated - }).Create(user).WithContext(r.Context()).Error; err != nil { - slog.Error("failed to create user", "err", err, "user", user) + if err := s.dao.UpsertUser(r.Context(), user); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -139,9 +151,16 @@ func (s *Server) loginCallbackHandler(w http.ResponseWriter, r *http.Request) { } func (s *Server) userHandler(w http.ResponseWriter, r *http.Request) { - userData, ok := r.Context().Value(ctxKeyTelegramUser).(*TelegramUser) + userData, ok := s.getTelegramUserData(r) if !ok { - http.Error(w, "no user data", http.StatusUnauthorized) + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + + probeCount, err := s.dao.CountProbes(r.Context(), userData.ID) + if err != nil { + slog.Error("failed to count probes", "err", err) + http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -150,7 +169,7 @@ func (s *Server) userHandler(w http.ResponseWriter, r *http.Request) { "User Info", nil, authedNavBar(userData), - userPage(userData), + userPage(userData, probeCount), ), ).ServeHTTP(w, r) } diff --git a/cmd/hdrwtch/probes.go b/cmd/hdrwtch/probes.go index b9a8c3a..255ac4d 100644 --- a/cmd/hdrwtch/probes.go +++ b/cmd/hdrwtch/probes.go @@ -3,6 +3,7 @@ package main import ( "log/slog" "net/http" + "time" "github.com/a-h/templ" "gorm.io/gorm" @@ -14,16 +15,19 @@ type Probe struct { UserID string Name string URL string - LastResultID int + LastResultID uint LastResult ProbeResult } type ProbeResult struct { gorm.Model - ProbeID int + ProbeID uint + Success bool LastModified string StatusCode int Region string + Remark string + Duration time.Duration } func (s *Server) probeList(w http.ResponseWriter, r *http.Request) { @@ -90,17 +94,13 @@ func (s *Server) probeEdit(w http.ResponseWriter, r *http.Request) { return } - probe, ok := getProbe(r.Context()) - if !ok { + probe, err := s.dao.GetProbe(r.Context(), r.PathValue("id"), tu.ID) + if err != nil { + slog.Error("failed to get probe", "path", r.URL.Path, "err", err) http.Error(w, "no probe data", http.StatusUnauthorized) return } - if probe.UserID != tu.ID { - http.Error(w, "unauthorized", http.StatusUnauthorized) - return - } - templ.Handler( probeEdit(*probe), ).ServeHTTP(w, r) @@ -114,18 +114,12 @@ func (s *Server) probeGet(w http.ResponseWriter, r *http.Request) { } probe, err := s.dao.GetProbe(r.Context(), r.PathValue("id"), tu.ID) - slog.Info("probe", "probe", probe) if err != nil { slog.Error("failed to get probe", "path", r.URL.Path, "err", err) http.Error(w, "no probe data", http.StatusUnauthorized) return } - if probe.UserID != tu.ID { - http.Error(w, "unauthorized", http.StatusUnauthorized) - return - } - if htmx.Is(r) { templ.Handler( probeRow(*probe), @@ -140,7 +134,7 @@ func (s *Server) probeGet(w http.ResponseWriter, r *http.Request) { } templ.Handler( - base("Probe "+probe.Name, nil, authedNavBar(tu), probePage(*probe, results)), + base(probe.Name, nil, authedNavBar(tu), probePage(*probe, results)), ).ServeHTTP(w, r) } } @@ -152,16 +146,31 @@ func (s *Server) probeUpdate(w http.ResponseWriter, r *http.Request) { return } - probe, ok := getProbe(r.Context()) - if !ok { + probe, err := s.dao.GetProbe(r.Context(), r.PathValue("id"), tu.ID) + if err != nil { + slog.Error("failed to get probe", "path", r.URL.Path, "err", err) http.Error(w, "no probe data", http.StatusUnauthorized) return } - if probe.UserID != tu.ID { - http.Error(w, "unauthorized", http.StatusUnauthorized) + if err := r.ParseForm(); err != nil { + slog.Error("failed to parse form", "err", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + probe.Name = r.FormValue("name") + probe.URL = r.FormValue("url") + + if err := s.dao.db.Save(probe).Error; err != nil { + slog.Error("failed to update probe", "err", err) + http.Error(w, err.Error(), http.StatusInternalServerError) return } + + templ.Handler( + probeRow(*probe), + ).ServeHTTP(w, r) } func (s *Server) probeDelete(w http.ResponseWriter, r *http.Request) { @@ -171,14 +180,18 @@ func (s *Server) probeDelete(w http.ResponseWriter, r *http.Request) { return } - probe, ok := getProbe(r.Context()) - if !ok { + probe, err := s.dao.GetProbe(r.Context(), r.PathValue("id"), tu.ID) + if err != nil { + slog.Error("failed to get probe", "path", r.URL.Path, "err", err) http.Error(w, "no probe data", http.StatusUnauthorized) return } - if probe.UserID != tu.ID { - http.Error(w, "unauthorized", http.StatusUnauthorized) + if err := s.dao.db.Delete(probe).Error; err != nil { + slog.Error("failed to delete probe", "err", err) + http.Error(w, err.Error(), http.StatusInternalServerError) return } + + http.Redirect(w, r, "/probes", http.StatusFound) } diff --git a/cmd/hdrwtch/probes.templ b/cmd/hdrwtch/probes.templ index b21dc3c..8d4bf8d 100644 --- a/cmd/hdrwtch/probes.templ +++ b/cmd/hdrwtch/probes.templ @@ -7,21 +7,43 @@ import ( templ probeListPage(probes []Probe) { <div id="parent"> - <table class="table-auto p-4" id="table"> - <thead> - <tr> - <th class="py-2 px-6">Name</th> - <th class="py-2 px-6">URL</th> - <th class="py-2 px-6">Last Result</th> - <th class="py-2 px-6"></th> - </tr> - </thead> - <tbody hx-target="closest tr" hx-swap="outerHTML"> - for _, probe := range probes { - @probeRow(probe) - } - </tbody> - </table> + <div class="mt-8 flow-root"> + <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> + <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> + <table class="min-w-full divide-y divide-gray-300"> + <thead> + <tr> + <th + scope="col" + class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0" + >Name</th> + <th + scope="col" + class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900" + >URL</th> + <th + scope="col" + class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900" + >Last Result</th> + <th + scope="col" + class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900" + ></th> + </tr> + </thead> + <tbody + class="divide-y divide-gray-200" + hx-target="closest tr" + hx-swap="outerHTML" + > + for _, probe := range probes { + @probeRow(probe) + } + </tbody> + </table> + </div> + </div> + </div> @probeCreateForm() </div> } @@ -77,9 +99,13 @@ templ probeCreateForm() { templ probeRow(probe Probe) { <tr> - <td class="">{ probe.Name }</td> - <td class="">{ probe.URL }</td> - <td class=""><a href={ templ.SafeURL(fmt.Sprintf("/probe/%d/run/%d", probe.ID, probe.LastResult.ID)) }>{ probe.LastResult.LastModified }</a></td> + <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-0"><a href={ templ.SafeURL(fmt.Sprintf("/probe/%d", probe.ID)) }>{ probe.Name }</a></td> + <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500"><code>{ probe.URL }</code></td> + if probe.LastResult.CreatedAt.IsZero() { + <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">Not run yet</td> + } else { + <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500"><a href={ templ.SafeURL(fmt.Sprintf("/probe/%d/run/%d", probe.ID, probe.LastResult.ID)) }>{ probe.LastResult.CreatedAt.Format(time.RFC3339) }</a></td> + } <td> <button class="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2" @@ -221,38 +247,38 @@ templ probePage(probe Probe, history []ProbeResult) { <div class="sm:flex sm:items-center"> <div class="sm:flex-auto"> <h2 class="text-base font-semibold leading-6 text-gray-900">Run history</h2> - <p class="mt-2 text-sm text-gray-700">The top 15 runs of this probe.</p> + <p class="mt-2 text-sm text-gray-700">The most recent 15 runs of this probe.</p> </div> </div> - <div class="mt-8 flow-root"> - <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> - <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> - <table class="min-w-full divide-y divide-gray-300"> - <thead> - <tr> - <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0">Name</th> - <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Title</th> - <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Email</th> - <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Role</th> - <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-0"> - <span class="sr-only">Edit</span> - </th> - </tr> - </thead> - <tbody class="divide-y divide-gray-200"> - <tr> - <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-0">Lindsay Walton</td> - <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">Front-end Developer</td> - <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">lindsay.walton@example.com</td> - <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">Member</td> - <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-0"> - <a href="#" class="text-indigo-600 hover:text-indigo-900">Edit<span class="sr-only">, Lindsay Walton</span></a> - </td> - </tr> - </tbody> - </table> + if len(history) != 0 { + <div class="mt-8 flow-root"> + <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> + <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> + <table class="min-w-full divide-y divide-gray-300"> + <thead> + <tr> + <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0">Time</th> + <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Status code</th> + <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Region</th> + <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Last Modified</th> + </tr> + </thead> + <tbody class="divide-y divide-gray-200"> + for _, check := range history { + <tr> + <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-0">{ check.CreatedAt.Format(time.RFC3339) }</td> + <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{ fmt.Sprint(check.StatusCode) }</td> + <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{ check.Region }</td> + <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{ check.LastModified }</td> + </tr> + } + </tbody> + </table> + </div> </div> </div> - </div> + } else { + <p class="mt-8 flow-root">This probe has not been run yet, wait 15 minutes or so for it to be run.</p> + } </div> } diff --git a/cmd/hdrwtch/probes_templ.go b/cmd/hdrwtch/probes_templ.go index ee3c877..6f1f659 100644 --- a/cmd/hdrwtch/probes_templ.go +++ b/cmd/hdrwtch/probes_templ.go @@ -31,7 +31,7 @@ func probeListPage(probes []Probe) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div id=\"parent\"><table class=\"table-auto p-4\" id=\"table\"><thead><tr><th class=\"py-2 px-6\">Name</th><th class=\"py-2 px-6\">URL</th><th class=\"py-2 px-6\">Last Result</th><th class=\"py-2 px-6\"></th></tr></thead> <tbody hx-target=\"closest tr\" hx-swap=\"outerHTML\">") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div id=\"parent\"><div class=\"mt-8 flow-root\"><div class=\"-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8\"><div class=\"inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8\"><table class=\"min-w-full divide-y divide-gray-300\"><thead><tr><th scope=\"col\" class=\"py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0\">Name</th><th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900\">URL</th><th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900\">Last Result</th><th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900\"></th></tr></thead> <tbody class=\"divide-y divide-gray-200\" hx-target=\"closest tr\" hx-swap=\"outerHTML\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -41,7 +41,7 @@ func probeListPage(probes []Probe) templ.Component { return templ_7745c5c3_Err } } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</tbody></table>") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</tbody></table></div></div></div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -101,64 +101,88 @@ func probeRow(probe Probe) templ.Component { templ_7745c5c3_Var3 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td class=\"\">") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td class=\"whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-0\"><a href=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(probe.Name) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 80, Col: 27} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + var templ_7745c5c3_Var4 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/probe/%d", probe.ID)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4))) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td class=\"\">") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(probe.URL) + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(probe.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 81, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 102, Col: 163} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td class=\"\"><a href=\"") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></td><td class=\"whitespace-nowrap px-3 py-4 text-sm text-gray-500\"><code>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var6 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/probe/%d/run/%d", probe.ID, probe.LastResult.ID)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6))) + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(probe.URL) if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err + return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 103, Col: 81} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var7 string - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(probe.LastResult.LastModified) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 82, Col: 136} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</code></td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></td><td><button class=\"focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2\" hx-get=\"") + if probe.LastResult.CreatedAt.IsZero() { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<td class=\"whitespace-nowrap px-3 py-4 text-sm text-gray-500\">Not run yet</td>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<td class=\"whitespace-nowrap px-3 py-4 text-sm text-gray-500\"><a href=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/probe/%d/run/%d", probe.ID, probe.LastResult.ID)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var7))) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(probe.LastResult.CreatedAt.Format(time.RFC3339)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 107, Col: 204} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></td>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<td><button class=\"focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2\" hx-get=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/probe/%d/edit", probe.ID)) + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/probe/%d/edit", probe.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 86, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 112, Col: 52} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -183,21 +207,21 @@ func probeEdit(probe Probe) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var9 := templ.GetChildren(ctx) - if templ_7745c5c3_Var9 == nil { - templ_7745c5c3_Var9 = templ.NopComponent + templ_7745c5c3_Var10 := templ.GetChildren(ctx) + if templ_7745c5c3_Var10 == nil { + templ_7745c5c3_Var10 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr hx-trigger=\"cancel\" class=\"editing\" hx-get=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/probe/%d", probe.ID)) + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/probe/%d", probe.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 111, Col: 84} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 137, Col: 84} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -205,12 +229,12 @@ func probeEdit(probe Probe) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var11 string - templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(probe.Name) + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(probe.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 112, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 138, Col: 43} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -218,12 +242,12 @@ func probeEdit(probe Probe) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(probe.URL) + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(probe.URL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 113, Col: 41} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 139, Col: 41} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -231,12 +255,12 @@ func probeEdit(probe Probe) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var13 string - templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/probe/%d", probe.ID)) + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/probe/%d", probe.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 117, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 143, Col: 47} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -244,12 +268,12 @@ func probeEdit(probe Probe) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/probe/%d", probe.ID)) + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/probe/%d", probe.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 123, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 149, Col: 47} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -274,21 +298,21 @@ func probePage(probe Probe, history []ProbeResult) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var15 := templ.GetChildren(ctx) - if templ_7745c5c3_Var15 == nil { - templ_7745c5c3_Var15 = templ.NopComponent + templ_7745c5c3_Var16 := templ.GetChildren(ctx) + if templ_7745c5c3_Var16 == nil { + templ_7745c5c3_Var16 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"px-4 sm:px-6 lg:px-8\"><div class=\"mt-8 flow-root\"><div class=\"-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8\"><div class=\"inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8\"><table class=\"min-w-full divide-y divide-gray-300\"><tbody><tr><td class=\"whitespace-nowrap py-2 pl-4 pr-3 font-medium text-gray-900 sm:pl-0\">Name</td><td class=\"whitespace-nowrap py-2 pl-4 pr-3 text-gray-900 sm:pl-0\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(probe.Name) + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(probe.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 148, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 174, Col: 21} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -296,12 +320,12 @@ func probePage(probe Probe, history []ProbeResult) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var17 string - templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(probe.CreatedAt.Format(time.RFC3339)) + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(probe.CreatedAt.Format(time.RFC3339)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 160, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 186, Col: 47} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -309,12 +333,12 @@ func probePage(probe Probe, history []ProbeResult) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var18 string - templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(probe.URL) + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(probe.URL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 172, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `probes.templ`, Line: 198, Col: 26} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -327,12 +351,12 @@ func probePage(probe Probe, history []ProbeResult) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var19 string |
