mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-10-28 07:41:27 +00:00
feat: domain monitoring (merge plan M2, phase 1)
Port of the domain-intelligence pipeline from the old Beszel fork, adapted to Dash's SQLite + no-PocketBase architecture: - internal/domain: RDAP via IANA bootstrap (cached), TCP WHOIS :43 fallback with multi-format parser, DNS (NS/MX/TXT/CNAME/A/AAAA), TLS leaf cert details, ip-api.com geolocation, provider detection (DNS/email/hosting/CA). HTML scraping, paid APIs, and exec-based whois deliberately dropped. - API: domains CRUD + synchronous refresh + domain_checks snapshot history; refresh runs inside the monitor sweep on interval_h. - UI: /domains + /domains/:id pages, add/edit dialog, domain board widget (favicon + expiry countdown), palette + nav entries, domainId picker in the widget dialog. - Test seam: Server.lookup injects the refresh; domain tests run hermetic against a stubbed result. Verified live: github.com resolves MarkMonitor expiry 2028-10, Route 53 DNS, GitHub Pages hosting, Sectigo cert with 69d left. Authored By: TDvorak <[email protected]> Generated with Devin (https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
1ff1b29ae7
commit
f9fd1d9cf2
18 files changed
+2376
-14
No files matched your search
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tdvorak/dash/internal/domain"
|
||||
"github.com/tdvorak/dash/internal/widget"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -24,11 +25,12 @@ type Server struct {
|
||||
log *zap.Logger
|
||||
status *statusCache
|
||||
widgets *widgetCache
|
||||
// lookup performs domain refreshes; swappable so tests don't hit the
|
||||
// real WHOIS/RDAP/DNS path.
|
||||
lookup func(ctx context.Context, name string) *domain.Result
|
||||
}
|
||||
|
||||
// NewRouter builds the HTTP handler: zap access log, recovery, /api routes.
|
||||
// A non-nil ctx starts the monitor scheduler; nil keeps it off (tests).
|
||||
func NewRouter(ctx context.Context, logger *zap.Logger, db *sql.DB, iconsDir string) *gin.Engine {
|
||||
func newServer(logger *zap.Logger, db *sql.DB, iconsDir string) *Server {
|
||||
s := &Server{
|
||||
db: db,
|
||||
iconsDir: iconsDir,
|
||||
@@ -36,13 +38,25 @@ func NewRouter(ctx context.Context, logger *zap.Logger, db *sql.DB, iconsDir str
|
||||
status: newStatusCache(60 * time.Second),
|
||||
widgets: newWidgetCache(30 * time.Second),
|
||||
}
|
||||
s.lookup = domain.NewLookup().Run
|
||||
widget.Register(&monitorWidget{db: db})
|
||||
widget.Register(&domainWidget{db: db})
|
||||
return s
|
||||
}
|
||||
|
||||
// NewRouter builds the HTTP handler: zap access log, recovery, /api routes.
|
||||
// A non-nil ctx starts the monitor scheduler; nil keeps it off (tests).
|
||||
func NewRouter(ctx context.Context, logger *zap.Logger, db *sql.DB, iconsDir string) *gin.Engine {
|
||||
s := newServer(logger, db, iconsDir)
|
||||
if ctx != nil {
|
||||
go s.runScheduler(ctx.Done())
|
||||
}
|
||||
return s.routes()
|
||||
}
|
||||
|
||||
func (s *Server) routes() *gin.Engine {
|
||||
r := gin.New()
|
||||
r.Use(accessLog(logger), gin.Recovery())
|
||||
r.Use(accessLog(s.log), gin.Recovery())
|
||||
|
||||
v1 := r.Group("/api")
|
||||
v1.GET("/healthz", s.healthz)
|
||||
@@ -69,6 +83,14 @@ func NewRouter(ctx context.Context, logger *zap.Logger, db *sql.DB, iconsDir str
|
||||
v1.POST("/monitors/:id/check", s.checkNow)
|
||||
v1.Any("/push/:token", s.pushIngest)
|
||||
|
||||
v1.GET("/domains", s.listDomainsH)
|
||||
v1.POST("/domains", s.createDomain)
|
||||
v1.GET("/domains/:id", s.getDomain)
|
||||
v1.PATCH("/domains/:id", s.patchDomain)
|
||||
v1.DELETE("/domains/:id", s.deleteDomain)
|
||||
v1.POST("/domains/:id/refresh", s.refreshDomainH)
|
||||
v1.GET("/domains/:id/checks", s.domainChecks)
|
||||
|
||||
v1.GET("/status", s.getStatus)
|
||||
v1.GET("/widgets/types", s.widgetTypes)
|
||||
v1.GET("/widgets/:id/data", s.widgetData)
|
||||
|
||||
Reference in new issue
Block a user