From d71a7e37c5058cb1f44313d15dc98ae7cd2f5cc1 Mon Sep 17 00:00:00 2001 From: "Mariano Z." Date: Mon, 16 Jun 2025 09:37:04 -0300 Subject: [PATCH] dev: automated commit - 2025-06-16 09:37:04 --- main.go | 427 ++++++++++++++++++++---------------- templates/index.templ | 336 +++++++++++++++++++---------- templates/index_templ.go | 443 +++++++++++++++++++++++++++++++++++++- templates/layout.templ | 52 +++++ templates/layout_templ.go | 6 +- templates/modal.templ | 297 +++++++++++++++---------- templates/modal_templ.go | 382 +++++++++++++++++++++++++++++++- 7 files changed, 1513 insertions(+), 430 deletions(-) diff --git a/main.go b/main.go index fbfcd26..6203c55 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "log" "net/http" "os" + "strconv" "strings" "time" @@ -21,25 +22,6 @@ import ( "github.com/robfig/cron/v3" ) -type DNSRecord struct { - ID string `json:"id"` - Type string `json:"type"` - Name string `json:"name"` - Content string `json:"content"` - TTL int `json:"ttl"` - Proxied bool `json:"proxied"` - CreatedOn string `json:"created_on"` -} - -type UpdateFrequency struct { - Label string `json:"label"` - Value string `json:"value"` -} - -type ErrorResponse struct { - Error string `json:"error"` -} - var ( api *cloudflare.API scheduler *cron.Cron @@ -66,7 +48,6 @@ func initDatabase() (*sql.DB, error) { } queries = db.New(sqlDB) - return sqlDB, nil } @@ -99,7 +80,7 @@ func getCurrentIP() (string, error) { return string(ip), nil } -func getDNSRecords(zoneID string) ([]DNSRecord, error) { +func getDNSRecords(zoneID string) ([]templates.DNSRecord, error) { if api == nil { return nil, fmt.Errorf("cloudflare API not initialized") } @@ -112,9 +93,9 @@ func getDNSRecords(zoneID string) ([]DNSRecord, error) { return nil, fmt.Errorf("failed to get DNS records: %w", err) } - var records []DNSRecord + var records []templates.DNSRecord for _, rec := range recs { - records = append(records, DNSRecord{ + records = append(records, templates.DNSRecord{ ID: rec.ID, Type: rec.Type, Name: rec.Name, @@ -271,6 +252,18 @@ func scheduleUpdates(zoneID, updatePeriod string) error { return nil } +func getUpdateFrequencies() []templates.UpdateFrequency { + return []templates.UpdateFrequency{ + {Label: "Every 1 minute", Value: "*/1 * * * *"}, + {Label: "Every 5 minutes", Value: "*/5 * * * *"}, + {Label: "Every 30 minutes", Value: "*/30 * * * *"}, + {Label: "Hourly", Value: "0 * * * *"}, + {Label: "Every 6 hours", Value: "0 */6 * * *"}, + {Label: "Daily", Value: "0 0 * * *"}, + {Label: "Never (manual only)", Value: ""}, + } +} + func main() { sqlDB, err := initDatabase() if err != nil { @@ -306,191 +299,259 @@ func main() { e.Use(middleware.Recover()) e.Static("/assets", "assets") - apiGroup := e.Group("/api") - { - apiGroup.GET("/config", func(c echo.Context) error { - configStatus := struct { - IsConfigured bool `json:"is_configured"` - ZoneID string `json:"zone_id"` - Domain string `json:"domain"` - UpdatePeriod string `json:"update_period"` - }{ - IsConfigured: config.ApiToken != "" && config.ZoneID != "", + // Main page + e.GET("/", func(c echo.Context) error { + currentIP, _ := getCurrentIP() + + var records []templates.DNSRecord + isConfigured := config.ApiToken != "" && config.ZoneID != "" + + if isConfigured { + records, _ = getDNSRecords(config.ZoneID) + } + + component := templates.Index(templates.IndexProps{ + Title: "mz.uy DNS Manager", + IsConfigured: isConfigured, + CurrentIP: currentIP, + Config: templates.ConfigData{ ZoneID: config.ZoneID, Domain: config.Domain, UpdatePeriod: config.UpdatePeriod, - } - return c.JSON(http.StatusOK, configStatus) + ApiToken: config.ApiToken, + }, + Records: records, + UpdateFreqs: getUpdateFrequencies(), }) + return templates.Render(c.Response(), component) + }) - apiGroup.POST("/config", func(c echo.Context) error { - var newConfig struct { - APIToken string `json:"api_token"` - ZoneID string `json:"zone_id"` - Domain string `json:"domain"` - UpdatePeriod string `json:"update_period"` - } + // Refresh current IP + e.GET("/refresh-ip", func(c echo.Context) error { + ip, err := getCurrentIP() + if err != nil { + c.Response().Header().Set("HX-Error-Message", "Failed to get current IP") + return c.String(http.StatusInternalServerError, "Error") + } + return c.String(http.StatusOK, ip) + }) - if err := c.Bind(&newConfig); err != nil { - return c.JSON(http.StatusBadRequest, ErrorResponse{Error: "Invalid request"}) - } + // Configuration + e.POST("/config", func(c echo.Context) error { + apiToken := c.FormValue("api_token") + zoneID := c.FormValue("zone_id") + domain := c.FormValue("domain") + updatePeriod := c.FormValue("update_period") - err := queries.UpsertConfig(context.Background(), db.UpsertConfigParams{ - ApiToken: newConfig.APIToken, - ZoneID: newConfig.ZoneID, - Domain: newConfig.Domain, - UpdatePeriod: newConfig.UpdatePeriod, - }) + if apiToken == "" || zoneID == "" || domain == "" { + c.Response().Header().Set("HX-Error-Message", "Please fill all required fields") + return c.String(http.StatusBadRequest, "Invalid input") + } + + err := queries.UpsertConfig(context.Background(), db.UpsertConfigParams{ + ApiToken: apiToken, + ZoneID: zoneID, + Domain: domain, + UpdatePeriod: updatePeriod, + }) + if err != nil { + c.Response().Header().Set("HX-Error-Message", "Failed to save configuration") + return c.String(http.StatusInternalServerError, "Database error") + } + + config.ApiToken = apiToken + config.ZoneID = zoneID + config.Domain = domain + config.UpdatePeriod = updatePeriod + + if err := initCloudflare(config.ApiToken, config.ZoneID); err != nil { + c.Response().Header().Set("HX-Error-Message", "Failed to initialize Cloudflare client") + return c.String(http.StatusInternalServerError, "API error") + } + + if err := scheduleUpdates(config.ZoneID, config.UpdatePeriod); err != nil { + c.Response().Header().Set("HX-Error-Message", "Failed to schedule updates") + return c.String(http.StatusInternalServerError, "Scheduler error") + } + + c.Response().Header().Set("HX-Success-Message", "Configuration saved successfully") + return c.Redirect(http.StatusSeeOther, "/") + }) + + // Create DNS record + e.POST("/records", func(c echo.Context) error { + if config.ApiToken == "" || config.ZoneID == "" { + c.Response().Header().Set("HX-Error-Message", "API not configured") + return c.String(http.StatusBadRequest, "Not configured") + } + + name := c.FormValue("name") + recordType := c.FormValue("type") + content := c.FormValue("content") + ttlStr := c.FormValue("ttl") + proxied := c.FormValue("proxied") == "on" + useMyIP := c.FormValue("use_my_ip") == "on" + + if name == "" { + c.Response().Header().Set("HX-Error-Message", "Name is required") + return c.String(http.StatusBadRequest, "Invalid input") + } + + ttl, err := strconv.Atoi(ttlStr) + if err != nil { + ttl = 1 + } + + if useMyIP { + currentIP, err := getCurrentIP() if err != nil { - return c.JSON(http.StatusInternalServerError, ErrorResponse{Error: fmt.Sprintf("Failed to save configuration: %v", err)}) + c.Response().Header().Set("HX-Error-Message", "Failed to get current IP") + return c.String(http.StatusInternalServerError, "IP error") } + content = currentIP + } - config.ApiToken = newConfig.APIToken - config.ZoneID = newConfig.ZoneID - config.Domain = newConfig.Domain - config.UpdatePeriod = newConfig.UpdatePeriod + if content == "" { + c.Response().Header().Set("HX-Error-Message", "Content is required") + return c.String(http.StatusBadRequest, "Invalid input") + } - if err := initCloudflare(config.ApiToken, config.ZoneID); err != nil { - return c.JSON(http.StatusInternalServerError, ErrorResponse{Error: fmt.Sprintf("Failed to initialize Cloudflare client: %v", err)}) - } + err = createDNSRecord(config.ZoneID, config.Domain, name, recordType, content, ttl, proxied) + if err != nil { + c.Response().Header().Set("HX-Error-Message", "Failed to create DNS record") + return c.String(http.StatusInternalServerError, "DNS error") + } - if err := scheduleUpdates(config.ZoneID, config.UpdatePeriod); err != nil { - return c.JSON(http.StatusInternalServerError, ErrorResponse{Error: fmt.Sprintf("Failed to schedule updates: %v", err)}) - } + c.Response().Header().Set("HX-Success-Message", "DNS record created successfully") - return c.JSON(http.StatusOK, map[string]string{"message": "Configuration updated successfully"}) - }) + // Return updated records table + records, _ := getDNSRecords(config.ZoneID) + currentIP, _ := getCurrentIP() + component := templates.DNSRecordsTable(records, currentIP) + return templates.Render(c.Response(), component) + }) - apiGroup.GET("/update-frequencies", func(c echo.Context) error { - frequencies := []UpdateFrequency{ - {Label: "Every 1 minutes", Value: "*/1 * * * *"}, - {Label: "Every 5 minutes", Value: "*/5 * * * *"}, - {Label: "Every 30 minutes", Value: "*/30 * * * *"}, - {Label: "Hourly", Value: "0 * * * *"}, - {Label: "Every 6 hours", Value: "0 */6 * * *"}, - {Label: "Daily", Value: "0 0 * * *"}, - {Label: "Never (manual only)", Value: ""}, - } - return c.JSON(http.StatusOK, frequencies) - }) + // Update DNS record + e.PUT("/records/:id", func(c echo.Context) error { + if config.ApiToken == "" || config.ZoneID == "" { + c.Response().Header().Set("HX-Error-Message", "API not configured") + return c.String(http.StatusBadRequest, "Not configured") + } - apiGroup.GET("/current-ip", func(c echo.Context) error { - ip, err := getCurrentIP() + id := c.Param("id") + name := c.FormValue("name") + recordType := c.FormValue("type") + content := c.FormValue("content") + ttlStr := c.FormValue("ttl") + proxied := c.FormValue("proxied") == "on" + useMyIP := c.FormValue("use_my_ip") == "on" + + ttl, err := strconv.Atoi(ttlStr) + if err != nil { + ttl = 1 + } + + if useMyIP { + currentIP, err := getCurrentIP() if err != nil { - return c.JSON(http.StatusInternalServerError, ErrorResponse{Error: fmt.Sprintf("Failed to get current IP: %v", err)}) + c.Response().Header().Set("HX-Error-Message", "Failed to get current IP") + return c.String(http.StatusInternalServerError, "IP error") } - return c.JSON(http.StatusOK, map[string]string{"ip": ip}) - }) + content = currentIP + } - apiGroup.GET("/records", func(c echo.Context) error { - if config.ApiToken == "" || config.ZoneID == "" { - return c.JSON(http.StatusBadRequest, ErrorResponse{Error: "API not configured"}) + // Convert name to full domain name if needed + fullName := name + if name != "@" && !strings.HasSuffix(name, config.Domain) { + fullName = name + "." + config.Domain + } + if name == "@" { + fullName = config.Domain + } + + err = updateDNSRecord(config.ZoneID, id, fullName, recordType, content, ttl, proxied) + if err != nil { + c.Response().Header().Set("HX-Error-Message", "Failed to update DNS record") + return c.String(http.StatusInternalServerError, "DNS error") + } + + c.Response().Header().Set("HX-Success-Message", "DNS record updated successfully") + + // Return updated records table + records, _ := getDNSRecords(config.ZoneID) + currentIP, _ := getCurrentIP() + component := templates.DNSRecordsTable(records, currentIP) + return templates.Render(c.Response(), component) + }) + + // Delete DNS record + e.DELETE("/records/:id", func(c echo.Context) error { + if config.ApiToken == "" || config.ZoneID == "" { + c.Response().Header().Set("HX-Error-Message", "API not configured") + return c.String(http.StatusBadRequest, "Not configured") + } + + id := c.Param("id") + err := deleteDNSRecord(config.ZoneID, id) + if err != nil { + c.Response().Header().Set("HX-Error-Message", "Failed to delete DNS record") + return c.String(http.StatusInternalServerError, "DNS error") + } + + c.Response().Header().Set("HX-Success-Message", "DNS record deleted successfully") + return c.String(http.StatusOK, "") + }) + + // Edit record form + e.GET("/edit-record/:id", func(c echo.Context) error { + if config.ApiToken == "" || config.ZoneID == "" { + c.Response().Header().Set("HX-Error-Message", "API not configured") + return c.String(http.StatusBadRequest, "Not configured") + } + + id := c.Param("id") + records, err := getDNSRecords(config.ZoneID) + if err != nil { + c.Response().Header().Set("HX-Error-Message", "Failed to load DNS records") + return c.String(http.StatusInternalServerError, "DNS error") + } + + var record templates.DNSRecord + for _, r := range records { + if r.ID == id { + record = r + break } + } - records, err := getDNSRecords(config.ZoneID) - if err != nil { - return c.JSON(http.StatusInternalServerError, ErrorResponse{Error: fmt.Sprintf("Failed to get DNS records: %v", err)}) - } - return c.JSON(http.StatusOK, records) - }) + if record.ID == "" { + c.Response().Header().Set("HX-Error-Message", "Record not found") + return c.String(http.StatusNotFound, "Not found") + } - apiGroup.POST("/records", func(c echo.Context) error { - if config.ApiToken == "" || config.ZoneID == "" { - return c.JSON(http.StatusBadRequest, ErrorResponse{Error: "API not configured"}) - } + component := templates.RecordForm("Edit DNS Record", id, config.Domain, record) + return templates.Render(c.Response(), component) + }) - var record struct { - Name string `json:"name"` - Type string `json:"type"` - Content string `json:"content"` - TTL int `json:"ttl"` - Proxied bool `json:"proxied"` - UseMyIP bool `json:"use_my_ip"` - } + // Update all records with current IP + e.POST("/update-all-records", func(c echo.Context) error { + if config.ApiToken == "" || config.ZoneID == "" { + c.Response().Header().Set("HX-Error-Message", "API not configured") + return c.String(http.StatusBadRequest, "Not configured") + } - if err := c.Bind(&record); err != nil { - return c.JSON(http.StatusBadRequest, ErrorResponse{Error: "Invalid request"}) - } + err := updateAllRecordsWithCurrentIP(config.ZoneID) + if err != nil { + c.Response().Header().Set("HX-Error-Message", "Failed to update records") + return c.String(http.StatusInternalServerError, "Update error") + } - if record.UseMyIP { - ip, err := getCurrentIP() - if err != nil { - return c.JSON(http.StatusInternalServerError, ErrorResponse{Error: fmt.Sprintf("Failed to get current IP: %v", err)}) - } - record.Content = ip - } + c.Response().Header().Set("HX-Success-Message", "All A records updated with current IP") - if err := createDNSRecord(config.ZoneID, config.Domain, record.Name, record.Type, record.Content, record.TTL, record.Proxied); err != nil { - return c.JSON(http.StatusInternalServerError, ErrorResponse{Error: fmt.Sprintf("Failed to create DNS record: %v", err)}) - } - - return c.JSON(http.StatusOK, map[string]string{"message": "DNS record created successfully"}) - }) - - apiGroup.PUT("/records/:id", func(c echo.Context) error { - if config.ApiToken == "" || config.ZoneID == "" { - return c.JSON(http.StatusBadRequest, ErrorResponse{Error: "API not configured"}) - } - - id := c.Param("id") - var record struct { - Name string `json:"name"` - Type string `json:"type"` - Content string `json:"content"` - TTL int `json:"ttl"` - Proxied bool `json:"proxied"` - UseMyIP bool `json:"use_my_ip"` - } - - if err := c.Bind(&record); err != nil { - return c.JSON(http.StatusBadRequest, ErrorResponse{Error: "Invalid request"}) - } - - if record.UseMyIP { - ip, err := getCurrentIP() - if err != nil { - return c.JSON(http.StatusInternalServerError, ErrorResponse{Error: fmt.Sprintf("Failed to get current IP: %v", err)}) - } - record.Content = ip - } - - if err := updateDNSRecord(config.ZoneID, id, record.Name, record.Type, record.Content, record.TTL, record.Proxied); err != nil { - return c.JSON(http.StatusInternalServerError, ErrorResponse{Error: fmt.Sprintf("Failed to update DNS record: %v", err)}) - } - - return c.JSON(http.StatusOK, map[string]string{"message": "DNS record updated successfully"}) - }) - - apiGroup.DELETE("/records/:id", func(c echo.Context) error { - if config.ApiToken == "" || config.ZoneID == "" { - return c.JSON(http.StatusBadRequest, ErrorResponse{Error: "API not configured"}) - } - - id := c.Param("id") - if err := deleteDNSRecord(config.ZoneID, id); err != nil { - return c.JSON(http.StatusInternalServerError, ErrorResponse{Error: fmt.Sprintf("Failed to delete DNS record: %v", err)}) - } - - return c.JSON(http.StatusOK, map[string]string{"message": "DNS record deleted successfully"}) - }) - - apiGroup.POST("/update-all", func(c echo.Context) error { - if config.ApiToken == "" || config.ZoneID == "" { - return c.JSON(http.StatusBadRequest, ErrorResponse{Error: "API not configured"}) - } - - if err := updateAllRecordsWithCurrentIP(config.ZoneID); err != nil { - return c.JSON(http.StatusInternalServerError, ErrorResponse{Error: fmt.Sprintf("Failed to update records: %v", err)}) - } - - return c.JSON(http.StatusOK, map[string]string{"message": "All A records updated with current IP"}) - }) - } - - e.GET("/", func(c echo.Context) error { - component := templates.Index(templates.IndexProps{ - Title: "mz.uy DNS Manager", - }) + // Return updated records table + records, _ := getDNSRecords(config.ZoneID) + currentIP, _ := getCurrentIP() + component := templates.DNSRecordsTable(records, currentIP) return templates.Render(c.Response(), component) }) diff --git a/templates/index.templ b/templates/index.templ index 0ccd31e..c58a501 100644 --- a/templates/index.templ +++ b/templates/index.templ @@ -1,11 +1,39 @@ package templates -// IndexProps contains the properties for the Index component +import "fmt" +import "strings" + type IndexProps struct { - Title string + Title string + IsConfigured bool + CurrentIP string + Config ConfigData + Records []DNSRecord + UpdateFreqs []UpdateFrequency +} + +type ConfigData struct { + ZoneID string + Domain string + UpdatePeriod string + ApiToken string +} + +type DNSRecord struct { + ID string + Type string + Name string + Content string + TTL int + Proxied bool + CreatedOn string +} + +type UpdateFrequency struct { + Label string + Value string } -// Index is the main page component templ Index(props IndexProps) { @Layout(props.Title) {
@@ -15,122 +43,214 @@ templ Index(props IndexProps) {

{ props.Title }

Current IP: - + { props.CurrentIP }
- - - - - - - -
-
- Loading... -
-

Loading...

-
+ if !props.IsConfigured { + @ConfigWarning() + } else { + @ConfigStatus(props.Config) + @DNSRecordsSection(props.Records, props.CurrentIP) + } + @ConfigModal(props.Config, props.UpdateFreqs) + @RecordModal(props.Config.Domain) - @ConfigModal() - @RecordModal() -
- } } + +templ ConfigWarning() { +
+

Configuration Required

+

Please configure your Cloudflare API credentials to manage your DNS records.

+ +
+} + +templ ConfigStatus(config ConfigData) { +
+
+
Configuration
+ +
+
+
+
+ Domain: { config.Domain } +
+
+ Zone ID: { config.ZoneID } +
+
+ IP Update Schedule: + { formatUpdateSchedule(config.UpdatePeriod) } +
+
+
+
+} + +templ DNSRecordsSection(records []DNSRecord, currentIP string) { +
+
+
DNS Records
+
+ + +
+
+
+ @DNSRecordsTable(records, currentIP) +
+
+} + +templ DNSRecordsTable(records []DNSRecord, currentIP string) { +
+ + + + + + + + + + + + + if len(records) == 0 { + + + + } else { + for _, record := range records { + @DNSRecordRow(record, currentIP) + } + } + +
TypeNameContentTTLProxiedActions
No DNS records found
+
+} + +templ DNSRecordRow(record DNSRecord, currentIP string) { + + { record.Type } + { record.Name } + + { record.Content } + if record.Type == "A" { + if record.Content == currentIP { + Current IP + } else { + Outdated IP + } + } + + + if record.TTL == 1 { + Auto + } else { + { fmt.Sprintf("%ds", record.TTL) } + } + + + if record.Proxied { + + } + + + + + + +} + +// Helper function to format update schedule +func formatUpdateSchedule(period string) string { + switch period { + case "*/1 * * * *": + return "Every minute" + case "*/5 * * * *": + return "Every 5 minutes" + case "*/30 * * * *": + return "Every 30 minutes" + case "0 * * * *": + return "Hourly" + case "0 */6 * * *": + return "Every 6 hours" + case "0 0 * * *": + return "Daily" + case "": + return "Manual updates only" + default: + return period + } +} + +// Helper function to extract subdomain name +func getRecordName(fullName, domain string) string { + if fullName == domain { + return "@" + } + if strings.HasSuffix(fullName, "."+domain) { + return fullName[:len(fullName)-len(domain)-1] + } + return fullName +} diff --git a/templates/index_templ.go b/templates/index_templ.go index e574bc8..ef78652 100644 --- a/templates/index_templ.go +++ b/templates/index_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.857 +// templ: version: v0.3.898 package templates //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -8,12 +8,40 @@ package templates import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -// IndexProps contains the properties for the Index component +import "fmt" +import "strings" + type IndexProps struct { - Title string + Title string + IsConfigured bool + CurrentIP string + Config ConfigData + Records []DNSRecord + UpdateFreqs []UpdateFrequency +} + +type ConfigData struct { + ZoneID string + Domain string + UpdatePeriod string + ApiToken string +} + +type DNSRecord struct { + ID string + Type string + Name string + Content string + TTL int + Proxied bool + CreatedOn string +} + +type UpdateFrequency struct { + Label string + Value string } -// Index is the main page component func Index(props IndexProps) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context @@ -54,29 +82,57 @@ func Index(props IndexProps) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(props.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 15, Col: 23} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 43, Col: 23} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
Current IP:

Configuration Required

Please configure your Cloudflare API credentials to manage your DNS records.

Configuration
Domain: mz.uy
Zone ID:
IP Update Schedule:
DNS Records
TypeNameContentTTLProxiedActions
Loading...

Loading...

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
Current IP: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = ConfigModal().Render(ctx, templ_7745c5c3_Buffer) + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(props.CurrentIP) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 46, Col: 62} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = RecordModal().Render(ctx, templ_7745c5c3_Buffer) + if !props.IsConfigured { + templ_7745c5c3_Err = ConfigWarning().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = ConfigStatus(props.Config).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = DNSRecordsSection(props.Records, props.CurrentIP).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = ConfigModal(props.Config, props.UpdateFreqs).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + templ_7745c5c3_Err = RecordModal(props.Config.Domain).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -90,4 +146,371 @@ func Index(props IndexProps) templ.Component { }) } +func ConfigWarning() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var5 := templ.GetChildren(ctx) + if templ_7745c5c3_Var5 == nil { + templ_7745c5c3_Var5 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

Configuration Required

Please configure your Cloudflare API credentials to manage your DNS records.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func ConfigStatus(config ConfigData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var6 := templ.GetChildren(ctx) + if templ_7745c5c3_Var6 == nil { + templ_7745c5c3_Var6 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
Configuration
Domain: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(config.Domain) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 103, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
Zone ID: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(config.ZoneID) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 106, Col: 52} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
IP Update Schedule: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(formatUpdateSchedule(config.UpdatePeriod)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 110, Col: 54} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func DNSRecordsSection(records []DNSRecord, currentIP string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var10 := templ.GetChildren(ctx) + if templ_7745c5c3_Var10 == nil { + templ_7745c5c3_Var10 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
DNS Records
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = DNSRecordsTable(records, currentIP).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func DNSRecordsTable(records []DNSRecord, currentIP string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(records) == 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + for _, record := range records { + templ_7745c5c3_Err = DNSRecordRow(record, currentIP).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
TypeNameContentTTLProxiedActions
No DNS records found
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func DNSRecordRow(record DNSRecord, currentIP string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var12 := templ.GetChildren(ctx) + if templ_7745c5c3_Var12 == nil { + templ_7745c5c3_Var12 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(record.Type) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 178, Col: 19} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(record.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 179, Col: 19} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(record.Content) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 181, Col: 19} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if record.Type == "A" { + if record.Content == currentIP { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "Current IP") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "Outdated IP") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if record.TTL == 1 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "Auto") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%ds", record.TTL)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 194, Col: 36} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if record.Proxied { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// Helper function to format update schedule +func formatUpdateSchedule(period string) string { + switch period { + case "*/1 * * * *": + return "Every minute" + case "*/5 * * * *": + return "Every 5 minutes" + case "*/30 * * * *": + return "Every 30 minutes" + case "0 * * * *": + return "Hourly" + case "0 */6 * * *": + return "Every 6 hours" + case "0 0 * * *": + return "Daily" + case "": + return "Manual updates only" + default: + return period + } +} + +// Helper function to extract subdomain name +func getRecordName(fullName, domain string) string { + if fullName == domain { + return "@" + } + if strings.HasSuffix(fullName, "."+domain) { + return fullName[:len(fullName)-len(domain)-1] + } + return fullName +} + var _ = templruntime.GeneratedTemplate diff --git a/templates/layout.templ b/templates/layout.templ index 20ca98d..b4918e4 100644 --- a/templates/layout.templ +++ b/templates/layout.templ @@ -24,6 +24,7 @@ templ Layout(title string) { rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" /> + { children... } + } diff --git a/templates/layout_templ.go b/templates/layout_templ.go index 9bf56b8..7edbbae 100644 --- a/templates/layout_templ.go +++ b/templates/layout_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.857 +// templ: version: v0.3.898 package templates //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -51,7 +51,7 @@ func Layout(title string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -59,7 +59,7 @@ func Layout(title string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/modal.templ b/templates/modal.templ index a9e25d8..e04f715 100644 --- a/templates/modal.templ +++ b/templates/modal.templ @@ -1,7 +1,8 @@ package templates -// ConfigModal is the configuration dialog component -templ ConfigModal() { +import "fmt" + +templ ConfigModal(config ConfigData, frequencies []UpdateFrequency) { - } -// RecordModal is the DNS record dialog component -templ RecordModal() { +templ RecordModal(domain string) {