dev: automated commit - 2025-06-16 09:37:04

This commit is contained in:
Mariano Z. 2025-06-16 09:37:04 -03:00
parent 682f25edcd
commit d71a7e37c5
7 changed files with 1513 additions and 430 deletions

401
main.go
View file

@ -8,6 +8,7 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
@ -21,25 +22,6 @@ import (
"github.com/robfig/cron/v3" "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 ( var (
api *cloudflare.API api *cloudflare.API
scheduler *cron.Cron scheduler *cron.Cron
@ -66,7 +48,6 @@ func initDatabase() (*sql.DB, error) {
} }
queries = db.New(sqlDB) queries = db.New(sqlDB)
return sqlDB, nil return sqlDB, nil
} }
@ -99,7 +80,7 @@ func getCurrentIP() (string, error) {
return string(ip), nil return string(ip), nil
} }
func getDNSRecords(zoneID string) ([]DNSRecord, error) { func getDNSRecords(zoneID string) ([]templates.DNSRecord, error) {
if api == nil { if api == nil {
return nil, fmt.Errorf("cloudflare API not initialized") 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) return nil, fmt.Errorf("failed to get DNS records: %w", err)
} }
var records []DNSRecord var records []templates.DNSRecord
for _, rec := range recs { for _, rec := range recs {
records = append(records, DNSRecord{ records = append(records, templates.DNSRecord{
ID: rec.ID, ID: rec.ID,
Type: rec.Type, Type: rec.Type,
Name: rec.Name, Name: rec.Name,
@ -271,6 +252,18 @@ func scheduleUpdates(zoneID, updatePeriod string) error {
return nil 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() { func main() {
sqlDB, err := initDatabase() sqlDB, err := initDatabase()
if err != nil { if err != nil {
@ -306,191 +299,259 @@ func main() {
e.Use(middleware.Recover()) e.Use(middleware.Recover())
e.Static("/assets", "assets") e.Static("/assets", "assets")
apiGroup := e.Group("/api") // Main page
{ e.GET("/", func(c echo.Context) error {
apiGroup.GET("/config", func(c echo.Context) error { currentIP, _ := getCurrentIP()
configStatus := struct {
IsConfigured bool `json:"is_configured"` var records []templates.DNSRecord
ZoneID string `json:"zone_id"` isConfigured := config.ApiToken != "" && config.ZoneID != ""
Domain string `json:"domain"`
UpdatePeriod string `json:"update_period"` if isConfigured {
}{ records, _ = getDNSRecords(config.ZoneID)
IsConfigured: config.ApiToken != "" && config.ZoneID != "", }
component := templates.Index(templates.IndexProps{
Title: "mz.uy DNS Manager",
IsConfigured: isConfigured,
CurrentIP: currentIP,
Config: templates.ConfigData{
ZoneID: config.ZoneID, ZoneID: config.ZoneID,
Domain: config.Domain, Domain: config.Domain,
UpdatePeriod: config.UpdatePeriod, UpdatePeriod: config.UpdatePeriod,
} ApiToken: config.ApiToken,
return c.JSON(http.StatusOK, configStatus) },
Records: records,
UpdateFreqs: getUpdateFrequencies(),
})
return templates.Render(c.Response(), component)
}) })
apiGroup.POST("/config", func(c echo.Context) error { // Refresh current IP
var newConfig struct { e.GET("/refresh-ip", func(c echo.Context) error {
APIToken string `json:"api_token"` ip, err := getCurrentIP()
ZoneID string `json:"zone_id"` if err != nil {
Domain string `json:"domain"` c.Response().Header().Set("HX-Error-Message", "Failed to get current IP")
UpdatePeriod string `json:"update_period"` return c.String(http.StatusInternalServerError, "Error")
} }
return c.String(http.StatusOK, ip)
})
if err := c.Bind(&newConfig); err != nil { // Configuration
return c.JSON(http.StatusBadRequest, ErrorResponse{Error: "Invalid request"}) 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")
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{ err := queries.UpsertConfig(context.Background(), db.UpsertConfigParams{
ApiToken: newConfig.APIToken, ApiToken: apiToken,
ZoneID: newConfig.ZoneID, ZoneID: zoneID,
Domain: newConfig.Domain, Domain: domain,
UpdatePeriod: newConfig.UpdatePeriod, UpdatePeriod: updatePeriod,
}) })
if err != nil { 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 save configuration")
return c.String(http.StatusInternalServerError, "Database error")
} }
config.ApiToken = newConfig.APIToken config.ApiToken = apiToken
config.ZoneID = newConfig.ZoneID config.ZoneID = zoneID
config.Domain = newConfig.Domain config.Domain = domain
config.UpdatePeriod = newConfig.UpdatePeriod config.UpdatePeriod = updatePeriod
if err := initCloudflare(config.ApiToken, config.ZoneID); err != nil { 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)}) 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 { 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-Error-Message", "Failed to schedule updates")
return c.String(http.StatusInternalServerError, "Scheduler error")
} }
return c.JSON(http.StatusOK, map[string]string{"message": "Configuration updated successfully"}) c.Response().Header().Set("HX-Success-Message", "Configuration saved successfully")
return c.Redirect(http.StatusSeeOther, "/")
}) })
apiGroup.GET("/update-frequencies", func(c echo.Context) error { // Create DNS record
frequencies := []UpdateFrequency{ e.POST("/records", func(c echo.Context) error {
{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)
})
apiGroup.GET("/current-ip", func(c echo.Context) error {
ip, err := getCurrentIP()
if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{Error: fmt.Sprintf("Failed to get current IP: %v", err)})
}
return c.JSON(http.StatusOK, map[string]string{"ip": ip})
})
apiGroup.GET("/records", func(c echo.Context) error {
if config.ApiToken == "" || config.ZoneID == "" { if config.ApiToken == "" || config.ZoneID == "" {
return c.JSON(http.StatusBadRequest, ErrorResponse{Error: "API not configured"}) 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 {
c.Response().Header().Set("HX-Error-Message", "Failed to get current IP")
return c.String(http.StatusInternalServerError, "IP error")
}
content = currentIP
}
if content == "" {
c.Response().Header().Set("HX-Error-Message", "Content is required")
return c.String(http.StatusBadRequest, "Invalid input")
}
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")
}
c.Response().Header().Set("HX-Success-Message", "DNS record created successfully")
// Return updated records table
records, _ := getDNSRecords(config.ZoneID)
currentIP, _ := getCurrentIP()
component := templates.DNSRecordsTable(records, currentIP)
return templates.Render(c.Response(), component)
})
// 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")
}
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 {
c.Response().Header().Set("HX-Error-Message", "Failed to get current IP")
return c.String(http.StatusInternalServerError, "IP error")
}
content = currentIP
}
// 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) records, err := getDNSRecords(config.ZoneID)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{Error: fmt.Sprintf("Failed to get DNS records: %v", err)}) c.Response().Header().Set("HX-Error-Message", "Failed to load DNS records")
return c.String(http.StatusInternalServerError, "DNS error")
} }
return c.JSON(http.StatusOK, records)
var record templates.DNSRecord
for _, r := range records {
if r.ID == id {
record = r
break
}
}
if record.ID == "" {
c.Response().Header().Set("HX-Error-Message", "Record not found")
return c.String(http.StatusNotFound, "Not found")
}
component := templates.RecordForm("Edit DNS Record", id, config.Domain, record)
return templates.Render(c.Response(), component)
}) })
apiGroup.POST("/records", func(c echo.Context) error { // Update all records with current IP
e.POST("/update-all-records", func(c echo.Context) error {
if config.ApiToken == "" || config.ZoneID == "" { if config.ApiToken == "" || config.ZoneID == "" {
return c.JSON(http.StatusBadRequest, ErrorResponse{Error: "API not configured"}) c.Response().Header().Set("HX-Error-Message", "API not configured")
return c.String(http.StatusBadRequest, "Not configured")
} }
var record struct { err := updateAllRecordsWithCurrentIP(config.ZoneID)
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 { 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 update records")
} return c.String(http.StatusInternalServerError, "Update error")
record.Content = ip
} }
if err := createDNSRecord(config.ZoneID, config.Domain, record.Name, record.Type, record.Content, record.TTL, record.Proxied); err != nil { c.Response().Header().Set("HX-Success-Message", "All A records updated with current IP")
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"}) // Return updated records table
}) records, _ := getDNSRecords(config.ZoneID)
currentIP, _ := getCurrentIP()
apiGroup.PUT("/records/:id", func(c echo.Context) error { component := templates.DNSRecordsTable(records, currentIP)
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 templates.Render(c.Response(), component) return templates.Render(c.Response(), component)
}) })

View file

@ -1,11 +1,39 @@
package templates package templates
// IndexProps contains the properties for the Index component import "fmt"
import "strings"
type IndexProps struct { 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) { templ Index(props IndexProps) {
@Layout(props.Title) { @Layout(props.Title) {
<div class="container"> <div class="container">
@ -15,27 +43,38 @@ templ Index(props IndexProps) {
<h1>{ props.Title }</h1> <h1>{ props.Title }</h1>
<div class="current-ip d-flex align-items-center"> <div class="current-ip d-flex align-items-center">
<span class="me-2">Current IP:</span> <span class="me-2">Current IP:</span>
<span id="current-ip" class="fw-bold"></span> <span id="current-ip" class="fw-bold">{ props.CurrentIP }</span>
<button <button
id="refresh-ip"
class="btn btn-sm btn-outline-secondary ms-2" class="btn btn-sm btn-outline-secondary ms-2"
title="Refresh current IP" title="Refresh current IP"
hx-get="/refresh-ip"
hx-target="#current-ip"
hx-indicator="#refresh-spinner"
> >
<i class="bi bi-arrow-clockwise"></i> <i class="bi bi-arrow-clockwise"></i>
<span id="refresh-spinner" class="htmx-indicator spinner-border spinner-border-sm ms-1"></span>
</button> </button>
</div> </div>
</div> </div>
<!-- Configuration Warning --> if !props.IsConfigured {
<div @ConfigWarning()
id="config-warning" } else {
class="alert alert-warning config-warning" @ConfigStatus(props.Config)
style="display: none" @DNSRecordsSection(props.Records, props.CurrentIP)
> }
@ConfigModal(props.Config, props.UpdateFreqs)
@RecordModal(props.Config.Domain)
</div>
</div>
</div>
<div class="toast-container"></div>
}
}
templ ConfigWarning() {
<div class="alert alert-warning config-warning">
<h4>Configuration Required</h4> <h4>Configuration Required</h4>
<p> <p>Please configure your Cloudflare API credentials to manage your DNS records.</p>
Please configure your Cloudflare API credentials to manage your
DNS records.
</p>
<button <button
class="btn btn-primary" class="btn btn-primary"
data-bs-toggle="modal" data-bs-toggle="modal"
@ -44,11 +83,11 @@ templ Index(props IndexProps) {
Configure Now Configure Now
</button> </button>
</div> </div>
<!-- Configuration Status --> }
<div id="config-status" class="card mb-4" style="display: none">
<div templ ConfigStatus(config ConfigData) {
class="card-header d-flex justify-content-between align-items-center" <div class="card mb-4">
> <div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Configuration</h5> <h5 class="mb-0">Configuration</h5>
<button <button
class="btn btn-sm btn-outline-primary" class="btn btn-sm btn-outline-primary"
@ -61,43 +100,53 @@ templ Index(props IndexProps) {
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<strong>Domain:</strong> <span id="domain-name">mz.uy</span> <strong>Domain:</strong> <span>{ config.Domain }</span>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<strong>Zone ID:</strong> <span id="zone-id"></span> <strong>Zone ID:</strong> <span>{ config.ZoneID }</span>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<strong>IP Update Schedule:</strong> <strong>IP Update Schedule:</strong>
<span id="update-schedule"></span> <span>{ formatUpdateSchedule(config.UpdatePeriod) }</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- DNS Records Section --> }
<div id="dns-records-section" style="display: none">
templ DNSRecordsSection(records []DNSRecord, currentIP string) {
<div class="card"> <div class="card">
<div <div class="card-header d-flex justify-content-between align-items-center">
class="card-header d-flex justify-content-between align-items-center"
>
<h5 class="mb-0">DNS Records</h5> <h5 class="mb-0">DNS Records</h5>
<div> <div>
<button <button
id="update-all-records"
class="btn btn-sm btn-success me-2" class="btn btn-sm btn-success me-2"
hx-post="/update-all-records"
hx-target="#dns-records-table"
hx-confirm="Are you sure you want to update all A records to your current IP?"
hx-indicator="#update-all-spinner"
> >
<i class="bi bi-arrow-repeat"></i> Update All to Current IP <i class="bi bi-arrow-repeat"></i> Update All to Current IP
<span id="update-all-spinner" class="htmx-indicator spinner-border spinner-border-sm ms-1"></span>
</button> </button>
<button <button
class="btn btn-sm btn-primary" class="btn btn-sm btn-primary"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#recordModal" data-bs-target="#recordModal"
onclick="resetRecordForm()"
> >
<i class="bi bi-plus-lg"></i> Add Record <i class="bi bi-plus-lg"></i> Add Record
</button> </button>
</div> </div>
</div> </div>
<div class="card-body p-0"> <div class="card-body p-0">
<div class="table-responsive"> @DNSRecordsTable(records, currentIP)
</div>
</div>
}
templ DNSRecordsTable(records []DNSRecord, currentIP string) {
<div id="dns-records-table" class="table-responsive">
<table class="table table-striped table-hover mb-0"> <table class="table table-striped table-hover mb-0">
<thead> <thead>
<tr> <tr>
@ -109,28 +158,99 @@ templ Index(props IndexProps) {
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody id="dns-records"> <tbody>
<!-- DNS records will be inserted here --> if len(records) == 0 {
<tr>
<td colspan="6" class="text-center">No DNS records found</td>
</tr>
} else {
for _, record := range records {
@DNSRecordRow(record, currentIP)
}
}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> }
</div>
</div> templ DNSRecordRow(record DNSRecord, currentIP string) {
<!-- Loading Indicator --> <tr>
<div id="loading" class="text-center my-5"> <td>{ record.Type }</td>
<div class="spinner-border" role="status"> <td>{ record.Name }</td>
<span class="visually-hidden">Loading...</span> <td>
</div> { record.Content }
<p class="mt-2">Loading...</p> if record.Type == "A" {
</div> if record.Content == currentIP {
</div> <span class="badge bg-success update-badge">Current IP</span>
</div> } else {
</div> <span class="badge bg-warning update-badge">Outdated IP</span>
@ConfigModal()
@RecordModal()
<!-- Toast container for notifications -->
<div class="toast-container"></div>
<script src="/assets/js/app.js"></script>
} }
} }
</td>
<td>
if record.TTL == 1 {
Auto
} else {
{ fmt.Sprintf("%ds", record.TTL) }
}
</td>
<td>
if record.Proxied {
<i class="bi bi-check-lg text-success"></i>
}
</td>
<td>
<button
class="btn btn-sm btn-outline-primary me-1"
hx-get={ fmt.Sprintf("/edit-record/%s", record.ID) }
hx-target="#record-modal-content"
data-bs-toggle="modal"
data-bs-target="#recordModal"
>
<i class="bi bi-pencil"></i>
</button>
<button
class="btn btn-sm btn-outline-danger"
hx-delete={ fmt.Sprintf("/records/%s", record.ID) }
hx-target="closest tr"
hx-swap="outerHTML"
hx-confirm={ fmt.Sprintf("Are you sure you want to delete the record for \"%s\"?", record.Name) }
>
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
}
// 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
}

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.857 // templ: version: v0.3.898
package templates package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present. //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 "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime" import templruntime "github.com/a-h/templ/runtime"
// IndexProps contains the properties for the Index component import "fmt"
import "strings"
type IndexProps struct { 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 { func Index(props IndexProps) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 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 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 var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(props.Title) templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(props.Title)
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</h1><div class=\"current-ip d-flex align-items-center\"><span class=\"me-2\">Current IP:</span> <span id=\"current-ip\" class=\"fw-bold\"></span> <button id=\"refresh-ip\" class=\"btn btn-sm btn-outline-secondary ms-2\" title=\"Refresh current IP\"><i class=\"bi bi-arrow-clockwise\"></i></button></div></div><!-- Configuration Warning --><div id=\"config-warning\" class=\"alert alert-warning config-warning\" style=\"display: none\"><h4>Configuration Required</h4><p>Please configure your Cloudflare API credentials to manage your DNS records.</p><button class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#configModal\">Configure Now</button></div><!-- Configuration Status --><div id=\"config-status\" class=\"card mb-4\" style=\"display: none\"><div class=\"card-header d-flex justify-content-between align-items-center\"><h5 class=\"mb-0\">Configuration</h5><button class=\"btn btn-sm btn-outline-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#configModal\">Edit</button></div><div class=\"card-body\"><div class=\"row\"><div class=\"col-md-4\"><strong>Domain:</strong> <span id=\"domain-name\">mz.uy</span></div><div class=\"col-md-4\"><strong>Zone ID:</strong> <span id=\"zone-id\"></span></div><div class=\"col-md-4\"><strong>IP Update Schedule:</strong> <span id=\"update-schedule\"></span></div></div></div></div><!-- DNS Records Section --><div id=\"dns-records-section\" style=\"display: none\"><div class=\"card\"><div class=\"card-header d-flex justify-content-between align-items-center\"><h5 class=\"mb-0\">DNS Records</h5><div><button id=\"update-all-records\" class=\"btn btn-sm btn-success me-2\"><i class=\"bi bi-arrow-repeat\"></i> Update All to Current IP</button> <button class=\"btn btn-sm btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#recordModal\"><i class=\"bi bi-plus-lg\"></i> Add Record</button></div></div><div class=\"card-body p-0\"><div class=\"table-responsive\"><table class=\"table table-striped table-hover mb-0\"><thead><tr><th>Type</th><th>Name</th><th>Content</th><th>TTL</th><th>Proxied</th><th>Actions</th></tr></thead> <tbody id=\"dns-records\"><!-- DNS records will be inserted here --></tbody></table></div></div></div></div><!-- Loading Indicator --><div id=\"loading\" class=\"text-center my-5\"><div class=\"spinner-border\" role=\"status\"><span class=\"visually-hidden\">Loading...</span></div><p class=\"mt-2\">Loading...</p></div></div></div></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</h1><div class=\"current-ip d-flex align-items-center\"><span class=\"me-2\">Current IP:</span> <span id=\"current-ip\" class=\"fw-bold\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err 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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</span> <button class=\"btn btn-sm btn-outline-secondary ms-2\" title=\"Refresh current IP\" hx-get=\"/refresh-ip\" hx-target=\"#current-ip\" hx-indicator=\"#refresh-spinner\"><i class=\"bi bi-arrow-clockwise\"></i> <span id=\"refresh-spinner\" class=\"htmx-indicator spinner-border spinner-border-sm ms-1\"></span></button></div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err 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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " <!-- Toast container for notifications --> <div class=\"toast-container\"></div><script src=\"/assets/js/app.js\"></script>") } 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 = 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, "</div></div></div><div class=\"toast-container\"></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err 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, "<div class=\"alert alert-warning config-warning\"><h4>Configuration Required</h4><p>Please configure your Cloudflare API credentials to manage your DNS records.</p><button class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#configModal\">Configure Now</button></div>")
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, "<div class=\"card mb-4\"><div class=\"card-header d-flex justify-content-between align-items-center\"><h5 class=\"mb-0\">Configuration</h5><button class=\"btn btn-sm btn-outline-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#configModal\">Edit</button></div><div class=\"card-body\"><div class=\"row\"><div class=\"col-md-4\"><strong>Domain:</strong> <span>")
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, "</span></div><div class=\"col-md-4\"><strong>Zone ID:</strong> <span>")
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, "</span></div><div class=\"col-md-4\"><strong>IP Update Schedule:</strong> <span>")
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, "</span></div></div></div></div>")
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, "<div class=\"card\"><div class=\"card-header d-flex justify-content-between align-items-center\"><h5 class=\"mb-0\">DNS Records</h5><div><button class=\"btn btn-sm btn-success me-2\" hx-post=\"/update-all-records\" hx-target=\"#dns-records-table\" hx-confirm=\"Are you sure you want to update all A records to your current IP?\" hx-indicator=\"#update-all-spinner\"><i class=\"bi bi-arrow-repeat\"></i> Update All to Current IP <span id=\"update-all-spinner\" class=\"htmx-indicator spinner-border spinner-border-sm ms-1\"></span></button> <button class=\"btn btn-sm btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#recordModal\" onclick=\"resetRecordForm()\"><i class=\"bi bi-plus-lg\"></i> Add Record</button></div></div><div class=\"card-body p-0\">")
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, "</div></div>")
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, "<div id=\"dns-records-table\" class=\"table-responsive\"><table class=\"table table-striped table-hover mb-0\"><thead><tr><th>Type</th><th>Name</th><th>Content</th><th>TTL</th><th>Proxied</th><th>Actions</th></tr></thead> <tbody>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(records) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<tr><td colspan=\"6\" class=\"text-center\">No DNS records found</td></tr>")
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, "</tbody></table></div>")
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, "<tr><td>")
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, "</td><td>")
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, "</td><td>")
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, "<span class=\"badge bg-success update-badge\">Current IP</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<span class=\"badge bg-warning update-badge\">Outdated IP</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</td><td>")
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, "</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.Proxied {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<i class=\"bi bi-check-lg text-success\"></i>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</td><td><button class=\"btn btn-sm btn-outline-primary me-1\" hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/edit-record/%s", record.ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 205, Col: 54}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\" hx-target=\"#record-modal-content\" data-bs-toggle=\"modal\" data-bs-target=\"#recordModal\"><i class=\"bi bi-pencil\"></i></button> <button class=\"btn btn-sm btn-outline-danger\" hx-delete=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/records/%s", record.ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 214, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\" hx-target=\"closest tr\" hx-swap=\"outerHTML\" hx-confirm=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Are you sure you want to delete the record for \"%s\"?", record.Name))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 217, Col: 99}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\"><i class=\"bi bi-trash\"></i></button></td></tr>")
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 var _ = templruntime.GeneratedTemplate

View file

@ -24,6 +24,7 @@ templ Layout(title string) {
rel="stylesheet" rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"
/> />
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<style> <style>
body { body {
padding-top: 20px; padding-top: 20px;
@ -46,11 +47,62 @@ templ Layout(title string) {
font-size: 0.8em; font-size: 0.8em;
margin-left: 10px; margin-left: 10px;
} }
.htmx-indicator {
opacity: 0;
transition: opacity 300ms ease-in;
}
.htmx-request .htmx-indicator {
opacity: 1;
}
.htmx-request.htmx-indicator {
opacity: 1;
}
</style> </style>
</head> </head>
<body> <body>
{ children... } { children... }
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Simple toast function for HTMX responses
document.body.addEventListener('htmx:afterRequest', function(evt) {
const xhr = evt.detail.xhr;
if (xhr.status >= 200 && xhr.status < 300) {
const successMessage = xhr.getResponseHeader('HX-Success-Message');
if (successMessage) {
showToast(successMessage, 'success');
}
} else {
const errorMessage = xhr.getResponseHeader('HX-Error-Message') || 'An error occurred';
showToast(errorMessage, 'danger');
}
});
function showToast(message, type = "success") {
const toastContainer = document.querySelector(".toast-container");
const toast = document.createElement("div");
toast.className = `toast align-items-center text-white bg-${type}`;
toast.setAttribute("role", "alert");
toast.setAttribute("aria-live", "assertive");
toast.setAttribute("aria-atomic", "true");
toast.innerHTML = `
<div class="d-flex">
<div class="toast-body">${message}</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
`;
toastContainer.appendChild(toast);
const bsToast = new bootstrap.Toast(toast);
bsToast.show();
toast.addEventListener("hidden.bs.toast", () => {
toast.remove();
});
}
</script>
</body> </body>
</html> </html>
} }

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.857 // templ: version: v0.3.898
package templates package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present. //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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\"><link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css\"><style>\n\t\t\t\tbody {\n\t\t\t\t\tpadding-top: 20px;\n\t\t\t\t\tbackground-color: #f8f9fa;\n\t\t\t\t}\n\t\t\t\t.card {\n\t\t\t\t\tmargin-bottom: 20px;\n\t\t\t\t\tbox-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);\n\t\t\t\t}\n\t\t\t\t.config-warning {\n\t\t\t\t\tmargin-bottom: 20px;\n\t\t\t\t}\n\t\t\t\t.toast-container {\n\t\t\t\t\tposition: fixed;\n\t\t\t\t\ttop: 20px;\n\t\t\t\t\tright: 20px;\n\t\t\t\t\tz-index: 1050;\n\t\t\t\t}\n\t\t\t\t.update-badge {\n\t\t\t\t\tfont-size: 0.8em;\n\t\t\t\t\tmargin-left: 10px;\n\t\t\t\t}\n\t\t\t</style></head><body>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\"><link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css\"><script src=\"https://unpkg.com/htmx.org@1.9.10\"></script><style>\n\t\t\t\tbody {\n\t\t\t\t\tpadding-top: 20px;\n\t\t\t\t\tbackground-color: #f8f9fa;\n\t\t\t\t}\n\t\t\t\t.card {\n\t\t\t\t\tmargin-bottom: 20px;\n\t\t\t\t\tbox-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);\n\t\t\t\t}\n\t\t\t\t.config-warning {\n\t\t\t\t\tmargin-bottom: 20px;\n\t\t\t\t}\n\t\t\t\t.toast-container {\n\t\t\t\t\tposition: fixed;\n\t\t\t\t\ttop: 20px;\n\t\t\t\t\tright: 20px;\n\t\t\t\t\tz-index: 1050;\n\t\t\t\t}\n\t\t\t\t.update-badge {\n\t\t\t\t\tfont-size: 0.8em;\n\t\t\t\t\tmargin-left: 10px;\n\t\t\t\t}\n\t\t\t\t.htmx-indicator {\n\t\t\t\t\topacity: 0;\n\t\t\t\t\ttransition: opacity 300ms ease-in;\n\t\t\t\t}\n\t\t\t\t.htmx-request .htmx-indicator {\n\t\t\t\t\topacity: 1;\n\t\t\t\t}\n\t\t\t\t.htmx-request.htmx-indicator {\n\t\t\t\t\topacity: 1;\n\t\t\t\t}\n\n\n\t\t\t</style></head><body>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -59,7 +59,7 @@ func Layout(title string) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js\"></script></body></html>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js\"></script><script>\n\t\t\t\t// Simple toast function for HTMX responses\n\t\t\t\tdocument.body.addEventListener('htmx:afterRequest', function(evt) {\n\t\t\t\t\tconst xhr = evt.detail.xhr;\n\t\t\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\t\t\tconst successMessage = xhr.getResponseHeader('HX-Success-Message');\n\t\t\t\t\t\tif (successMessage) {\n\t\t\t\t\t\t\tshowToast(successMessage, 'success');\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst errorMessage = xhr.getResponseHeader('HX-Error-Message') || 'An error occurred';\n\t\t\t\t\t\tshowToast(errorMessage, 'danger');\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tfunction showToast(message, type = \"success\") {\n\t\t\t\t\tconst toastContainer = document.querySelector(\".toast-container\");\n\t\t\t\t\tconst toast = document.createElement(\"div\");\n\t\t\t\t\ttoast.className = `toast align-items-center text-white bg-${type}`;\n\t\t\t\t\ttoast.setAttribute(\"role\", \"alert\");\n\t\t\t\t\ttoast.setAttribute(\"aria-live\", \"assertive\");\n\t\t\t\t\ttoast.setAttribute(\"aria-atomic\", \"true\");\n\n\t\t\t\t\ttoast.innerHTML = `\n\t\t\t\t\t\t<div class=\"d-flex\">\n\t\t\t\t\t\t\t<div class=\"toast-body\">${message}</div>\n\t\t\t\t\t\t\t<button type=\"button\" class=\"btn-close btn-close-white me-2 m-auto\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t`;\n\n\t\t\t\t\ttoastContainer.appendChild(toast);\n\t\t\t\t\tconst bsToast = new bootstrap.Toast(toast);\n\t\t\t\t\tbsToast.show();\n\n\t\t\t\t\ttoast.addEventListener(\"hidden.bs.toast\", () => {\n\t\t\t\t\t\ttoast.remove();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t</script></body></html>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View file

@ -1,7 +1,8 @@
package templates package templates
// ConfigModal is the configuration dialog component import "fmt"
templ ConfigModal() {
templ ConfigModal(config ConfigData, frequencies []UpdateFrequency) {
<div <div
class="modal fade" class="modal fade"
id="configModal" id="configModal"
@ -20,14 +21,16 @@ templ ConfigModal() {
aria-label="Close" aria-label="Close"
></button> ></button>
</div> </div>
<form hx-post="/config" hx-target="body" hx-swap="outerHTML">
<div class="modal-body"> <div class="modal-body">
<form id="config-form">
<div class="mb-3"> <div class="mb-3">
<label for="api-token" class="form-label">Cloudflare API Token</label> <label for="api-token" class="form-label">Cloudflare API Token</label>
<input <input
type="password" type="password"
class="form-control" class="form-control"
id="api-token" id="api-token"
name="api_token"
value={ config.ApiToken }
required required
/> />
<div class="form-text"> <div class="form-text">
@ -41,11 +44,12 @@ templ ConfigModal() {
type="text" type="text"
class="form-control" class="form-control"
id="zone-id-input" id="zone-id-input"
name="zone_id"
value={ config.ZoneID }
required required
/> />
<div class="form-text"> <div class="form-text">
Found in the Cloudflare dashboard under your domain's overview Found in the Cloudflare dashboard under your domain's overview page.
page.
</div> </div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
@ -54,17 +58,21 @@ templ ConfigModal() {
type="text" type="text"
class="form-control" class="form-control"
id="domain-input" id="domain-input"
value="mz.uy" name="domain"
value={ config.Domain }
required required
/> />
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="update-period" class="form-label">Update Frequency</label> <label for="update-period" class="form-label">Update Frequency</label>
<select class="form-select" id="update-period"> <select class="form-select" id="update-period" name="update_period">
<!-- Options will be loaded from API --> for _, freq := range frequencies {
<option value={ freq.Value } selected?={ freq.Value == config.UpdatePeriod }>
{ freq.Label }
</option>
}
</select> </select>
</div> </div>
</form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button <button
@ -74,17 +82,18 @@ templ ConfigModal() {
> >
Cancel Cancel
</button> </button>
<button type="button" class="btn btn-primary" id="save-config"> <button type="submit" class="btn btn-primary">
Save Save
<span class="htmx-indicator spinner-border spinner-border-sm ms-1"></span>
</button> </button>
</div> </div>
</form>
</div> </div>
</div> </div>
</div> </div>
} }
// RecordModal is the DNS record dialog component templ RecordModal(domain string) {
templ RecordModal() {
<div <div
class="modal fade" class="modal fade"
id="recordModal" id="recordModal"
@ -93,9 +102,16 @@ templ RecordModal() {
aria-hidden="true" aria-hidden="true"
> >
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div id="record-modal-content" class="modal-content">
@RecordForm("Add DNS Record", "", domain, DNSRecord{Type: "A", TTL: 1})
</div>
</div>
</div>
}
templ RecordForm(title, recordID, domain string, record DNSRecord) {
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="recordModalLabel">Add DNS Record</h5> <h5 class="modal-title">{ title }</h5>
<button <button
type="button" type="button"
class="btn-close" class="btn-close"
@ -103,9 +119,16 @@ templ RecordModal() {
aria-label="Close" aria-label="Close"
></button> ></button>
</div> </div>
<form
if recordID != "" {
hx-put={ fmt.Sprintf("/records/%s", recordID) }
} else {
hx-post="/records"
}
hx-target="#dns-records-table"
hx-on::after-request="if(event.detail.successful) bootstrap.Modal.getInstance(document.getElementById('recordModal')).hide()"
>
<div class="modal-body"> <div class="modal-body">
<form id="record-form">
<input type="hidden" id="record-id"/>
<div class="mb-3"> <div class="mb-3">
<label for="record-name" class="form-label">Name</label> <label for="record-name" class="form-label">Name</label>
<div class="input-group"> <div class="input-group">
@ -113,21 +136,28 @@ templ RecordModal() {
type="text" type="text"
class="form-control" class="form-control"
id="record-name" id="record-name"
name="name"
placeholder="subdomain" placeholder="subdomain"
value={ getRecordName(record.Name, domain) }
required required
/> />
<span class="input-group-text" id="domain-suffix">.mz.uy</span> <span class="input-group-text">.{ domain }</span>
</div> </div>
<div class="form-text">Use @ for the root domain</div> <div class="form-text">Use @ for the root domain</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="record-type" class="form-label">Type</label> <label for="record-type" class="form-label">Type</label>
<select class="form-select" id="record-type"> <select
<option value="A">A</option> class="form-select"
<option value="AAAA">AAAA</option> id="record-type"
<option value="CNAME">CNAME</option> name="type"
<option value="TXT">TXT</option> onchange="toggleMyIPOption()"
<option value="MX">MX</option> >
<option value="A" selected?={ record.Type == "A" }>A</option>
<option value="AAAA" selected?={ record.Type == "AAAA" }>AAAA</option>
<option value="CNAME" selected?={ record.Type == "CNAME" }>CNAME</option>
<option value="TXT" selected?={ record.Type == "TXT" }>TXT</option>
<option value="MX" selected?={ record.Type == "MX" }>MX</option>
</select> </select>
</div> </div>
<div class="mb-3" id="content-group"> <div class="mb-3" id="content-group">
@ -136,30 +166,34 @@ templ RecordModal() {
type="text" type="text"
class="form-control" class="form-control"
id="record-content" id="record-content"
name="content"
value={ record.Content }
required required
/> />
</div> </div>
<div class="mb-3 form-check" id="use-my-ip-group"> <div class="mb-3 form-check" id="use-my-ip-group" style="display: none;">
<input <input
type="checkbox" type="checkbox"
class="form-check-input" class="form-check-input"
id="use-my-ip" id="use-my-ip"
name="use_my_ip"
onchange="toggleContentField()"
/> />
<label class="form-check-label" for="use-my-ip">Use my current IP address</label> <label class="form-check-label" for="use-my-ip">Use my current IP address</label>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="record-ttl" class="form-label">TTL</label> <label for="record-ttl" class="form-label">TTL</label>
<select class="form-select" id="record-ttl"> <select class="form-select" id="record-ttl" name="ttl">
<option value="1">Auto</option> <option value="1" selected?={ record.TTL == 1 }>Auto</option>
<option value="120">2 minutes</option> <option value="120" selected?={ record.TTL == 120 }>2 minutes</option>
<option value="300">5 minutes</option> <option value="300" selected?={ record.TTL == 300 }>5 minutes</option>
<option value="600">10 minutes</option> <option value="600" selected?={ record.TTL == 600 }>10 minutes</option>
<option value="1800">30 minutes</option> <option value="1800" selected?={ record.TTL == 1800 }>30 minutes</option>
<option value="3600">1 hour</option> <option value="3600" selected?={ record.TTL == 3600 }>1 hour</option>
<option value="7200">2 hours</option> <option value="7200" selected?={ record.TTL == 7200 }>2 hours</option>
<option value="18000">5 hours</option> <option value="18000" selected?={ record.TTL == 18000 }>5 hours</option>
<option value="43200">12 hours</option> <option value="43200" selected?={ record.TTL == 43200 }>12 hours</option>
<option value="86400">1 day</option> <option value="86400" selected?={ record.TTL == 86400 }>1 day</option>
</select> </select>
</div> </div>
<div class="mb-3 form-check"> <div class="mb-3 form-check">
@ -167,10 +201,11 @@ templ RecordModal() {
type="checkbox" type="checkbox"
class="form-check-input" class="form-check-input"
id="record-proxied" id="record-proxied"
name="proxied"
checked?={ record.Proxied }
/> />
<label class="form-check-label" for="record-proxied">Proxied through Cloudflare</label> <label class="form-check-label" for="record-proxied">Proxied through Cloudflare</label>
</div> </div>
</form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button <button
@ -180,11 +215,41 @@ templ RecordModal() {
> >
Cancel Cancel
</button> </button>
<button type="button" class="btn btn-primary" id="save-record"> <button type="submit" class="btn btn-primary">
Save Save
<span class="htmx-indicator spinner-border spinner-border-sm ms-1"></span>
</button> </button>
</div> </div>
</div> </form>
</div> <script>
</div> function toggleMyIPOption() {
const recordType = document.getElementById('record-type').value;
const useMyIPGroup = document.getElementById('use-my-ip-group');
const contentGroup = document.getElementById('content-group');
if (recordType === 'A') {
useMyIPGroup.style.display = 'block';
} else {
useMyIPGroup.style.display = 'none';
contentGroup.style.display = 'block';
document.getElementById('use-my-ip').checked = false;
}
}
function toggleContentField() {
const useMyIP = document.getElementById('use-my-ip').checked;
const contentGroup = document.getElementById('content-group');
contentGroup.style.display = useMyIP ? 'none' : 'block';
}
function resetRecordForm() {
setTimeout(() => {
document.getElementById('record-type').value = 'A';
toggleMyIPOption();
}, 100);
}
// Initialize the form state
toggleMyIPOption();
</script>
} }

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.857 // templ: version: v0.3.898
package templates package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.
@ -8,8 +8,9 @@ package templates
import "github.com/a-h/templ" import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime" import templruntime "github.com/a-h/templ/runtime"
// ConfigModal is the configuration dialog component import "fmt"
func ConfigModal() templ.Component {
func ConfigModal(config ConfigData, frequencies []UpdateFrequency) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 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 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@ -30,7 +31,92 @@ func ConfigModal() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent templ_7745c5c3_Var1 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"modal fade\" id=\"configModal\" tabindex=\"-1\" aria-labelledby=\"configModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"configModalLabel\">Configuration</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><div class=\"modal-body\"><form id=\"config-form\"><div class=\"mb-3\"><label for=\"api-token\" class=\"form-label\">Cloudflare API Token</label> <input type=\"password\" class=\"form-control\" id=\"api-token\" required><div class=\"form-text\">Create a token with <code>Zone.DNS:Edit</code> permissions in the Cloudflare dashboard.</div></div><div class=\"mb-3\"><label for=\"zone-id-input\" class=\"form-label\">Zone ID</label> <input type=\"text\" class=\"form-control\" id=\"zone-id-input\" required><div class=\"form-text\">Found in the Cloudflare dashboard under your domain's overview page.</div></div><div class=\"mb-3\"><label for=\"domain-input\" class=\"form-label\">Domain</label> <input type=\"text\" class=\"form-control\" id=\"domain-input\" value=\"mz.uy\" required></div><div class=\"mb-3\"><label for=\"update-period\" class=\"form-label\">Update Frequency</label> <select class=\"form-select\" id=\"update-period\"><!-- Options will be loaded from API --></select></div></form></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"button\" class=\"btn btn-primary\" id=\"save-config\">Save</button></div></div></div></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"modal fade\" id=\"configModal\" tabindex=\"-1\" aria-labelledby=\"configModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"configModalLabel\">Configuration</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form hx-post=\"/config\" hx-target=\"body\" hx-swap=\"outerHTML\"><div class=\"modal-body\"><div class=\"mb-3\"><label for=\"api-token\" class=\"form-label\">Cloudflare API Token</label> <input type=\"password\" class=\"form-control\" id=\"api-token\" name=\"api_token\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(config.ApiToken)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/modal.templ`, Line: 33, Col: 31}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" required><div class=\"form-text\">Create a token with <code>Zone.DNS:Edit</code> permissions in the Cloudflare dashboard.</div></div><div class=\"mb-3\"><label for=\"zone-id-input\" class=\"form-label\">Zone ID</label> <input type=\"text\" class=\"form-control\" id=\"zone-id-input\" name=\"zone_id\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(config.ZoneID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/modal.templ`, Line: 48, Col: 29}
}
_, 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, 3, "\" required><div class=\"form-text\">Found in the Cloudflare dashboard under your domain's overview page.</div></div><div class=\"mb-3\"><label for=\"domain-input\" class=\"form-label\">Domain</label> <input type=\"text\" class=\"form-control\" id=\"domain-input\" name=\"domain\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(config.Domain)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/modal.templ`, Line: 62, Col: 29}
}
_, 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, 4, "\" required></div><div class=\"mb-3\"><label for=\"update-period\" class=\"form-label\">Update Frequency</label> <select class=\"form-select\" id=\"update-period\" name=\"update_period\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, freq := range frequencies {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<option value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(freq.Value)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/modal.templ`, Line: 70, Col: 35}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if freq.Value == config.UpdatePeriod {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, ">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(freq.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/modal.templ`, Line: 71, Col: 22}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</option>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</select></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\">Save <span class=\"htmx-indicator spinner-border spinner-border-sm ms-1\"></span></button></div></form></div></div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -38,8 +124,7 @@ func ConfigModal() templ.Component {
}) })
} }
// RecordModal is the DNS record dialog component func RecordModal(domain string) templ.Component {
func RecordModal() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 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 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@ -55,12 +140,289 @@ func RecordModal() templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx) templ_7745c5c3_Var7 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil { if templ_7745c5c3_Var7 == nil {
templ_7745c5c3_Var2 = templ.NopComponent templ_7745c5c3_Var7 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"modal fade\" id=\"recordModal\" tabindex=\"-1\" aria-labelledby=\"recordModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"recordModalLabel\">Add DNS Record</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><div class=\"modal-body\"><form id=\"record-form\"><input type=\"hidden\" id=\"record-id\"><div class=\"mb-3\"><label for=\"record-name\" class=\"form-label\">Name</label><div class=\"input-group\"><input type=\"text\" class=\"form-control\" id=\"record-name\" placeholder=\"subdomain\" required> <span class=\"input-group-text\" id=\"domain-suffix\">.mz.uy</span></div><div class=\"form-text\">Use @ for the root domain</div></div><div class=\"mb-3\"><label for=\"record-type\" class=\"form-label\">Type</label> <select class=\"form-select\" id=\"record-type\"><option value=\"A\">A</option> <option value=\"AAAA\">AAAA</option> <option value=\"CNAME\">CNAME</option> <option value=\"TXT\">TXT</option> <option value=\"MX\">MX</option></select></div><div class=\"mb-3\" id=\"content-group\"><label for=\"record-content\" class=\"form-label\">Content</label> <input type=\"text\" class=\"form-control\" id=\"record-content\" required></div><div class=\"mb-3 form-check\" id=\"use-my-ip-group\"><input type=\"checkbox\" class=\"form-check-input\" id=\"use-my-ip\"> <label class=\"form-check-label\" for=\"use-my-ip\">Use my current IP address</label></div><div class=\"mb-3\"><label for=\"record-ttl\" class=\"form-label\">TTL</label> <select class=\"form-select\" id=\"record-ttl\"><option value=\"1\">Auto</option> <option value=\"120\">2 minutes</option> <option value=\"300\">5 minutes</option> <option value=\"600\">10 minutes</option> <option value=\"1800\">30 minutes</option> <option value=\"3600\">1 hour</option> <option value=\"7200\">2 hours</option> <option value=\"18000\">5 hours</option> <option value=\"43200\">12 hours</option> <option value=\"86400\">1 day</option></select></div><div class=\"mb-3 form-check\"><input type=\"checkbox\" class=\"form-check-input\" id=\"record-proxied\"> <label class=\"form-check-label\" for=\"record-proxied\">Proxied through Cloudflare</label></div></form></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"button\" class=\"btn btn-primary\" id=\"save-record\">Save</button></div></div></div></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div class=\"modal fade\" id=\"recordModal\" tabindex=\"-1\" aria-labelledby=\"recordModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div id=\"record-modal-content\" class=\"modal-content\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = RecordForm("Add DNS Record", "", domain, DNSRecord{Type: "A", TTL: 1}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func RecordForm(title, recordID, domain string, record DNSRecord) 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_Var8 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil {
templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"modal-header\"><h5 class=\"modal-title\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/modal.templ`, Line: 114, Col: 33}
}
_, 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, 14, "</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if recordID != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " hx-put=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/records/%s", recordID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/modal.templ`, Line: 124, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, " hx-post=\"/records\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, " hx-target=\"#dns-records-table\" hx-on::after-request=\"if(event.detail.successful) bootstrap.Modal.getInstance(document.getElementById('recordModal')).hide()\"><div class=\"modal-body\"><div class=\"mb-3\"><label for=\"record-name\" class=\"form-label\">Name</label><div class=\"input-group\"><input type=\"text\" class=\"form-control\" id=\"record-name\" name=\"name\" placeholder=\"subdomain\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(getRecordName(record.Name, domain))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/modal.templ`, Line: 141, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" required> <span class=\"input-group-text\">.")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(domain)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/modal.templ`, Line: 144, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</span></div><div class=\"form-text\">Use @ for the root domain</div></div><div class=\"mb-3\"><label for=\"record-type\" class=\"form-label\">Type</label> <select class=\"form-select\" id=\"record-type\" name=\"type\" onchange=\"toggleMyIPOption()\"><option value=\"A\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.Type == "A" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, ">A</option> <option value=\"AAAA\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.Type == "AAAA" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, ">AAAA</option> <option value=\"CNAME\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.Type == "CNAME" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, ">CNAME</option> <option value=\"TXT\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.Type == "TXT" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, ">TXT</option> <option value=\"MX\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.Type == "MX" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, ">MX</option></select></div><div class=\"mb-3\" id=\"content-group\"><label for=\"record-content\" class=\"form-label\">Content</label> <input type=\"text\" class=\"form-control\" id=\"record-content\" name=\"content\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(record.Content)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/modal.templ`, Line: 170, Col: 27}
}
_, 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, 31, "\" required></div><div class=\"mb-3 form-check\" id=\"use-my-ip-group\" style=\"display: none;\"><input type=\"checkbox\" class=\"form-check-input\" id=\"use-my-ip\" name=\"use_my_ip\" onchange=\"toggleContentField()\"> <label class=\"form-check-label\" for=\"use-my-ip\">Use my current IP address</label></div><div class=\"mb-3\"><label for=\"record-ttl\" class=\"form-label\">TTL</label> <select class=\"form-select\" id=\"record-ttl\" name=\"ttl\"><option value=\"1\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.TTL == 1 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, ">Auto</option> <option value=\"120\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.TTL == 120 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, ">2 minutes</option> <option value=\"300\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.TTL == 300 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, ">5 minutes</option> <option value=\"600\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.TTL == 600 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, ">10 minutes</option> <option value=\"1800\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.TTL == 1800 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, ">30 minutes</option> <option value=\"3600\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.TTL == 3600 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, ">1 hour</option> <option value=\"7200\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.TTL == 7200 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, ">2 hours</option> <option value=\"18000\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.TTL == 18000 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, ">5 hours</option> <option value=\"43200\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.TTL == 43200 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, ">12 hours</option> <option value=\"86400\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.TTL == 86400 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, ">1 day</option></select></div><div class=\"mb-3 form-check\"><input type=\"checkbox\" class=\"form-check-input\" id=\"record-proxied\" name=\"proxied\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if record.Proxied {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, " checked")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "> <label class=\"form-check-label\" for=\"record-proxied\">Proxied through Cloudflare</label></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\">Save <span class=\"htmx-indicator spinner-border spinner-border-sm ms-1\"></span></button></div></form><script>\n\t\tfunction toggleMyIPOption() {\n\t\t\tconst recordType = document.getElementById('record-type').value;\n\t\t\tconst useMyIPGroup = document.getElementById('use-my-ip-group');\n\t\t\tconst contentGroup = document.getElementById('content-group');\n\n\t\t\tif (recordType === 'A') {\n\t\t\t\tuseMyIPGroup.style.display = 'block';\n\t\t\t} else {\n\t\t\t\tuseMyIPGroup.style.display = 'none';\n\t\t\t\tcontentGroup.style.display = 'block';\n\t\t\t\tdocument.getElementById('use-my-ip').checked = false;\n\t\t\t}\n\t\t}\n\n\t\tfunction toggleContentField() {\n\t\t\tconst useMyIP = document.getElementById('use-my-ip').checked;\n\t\t\tconst contentGroup = document.getElementById('content-group');\n\t\t\tcontentGroup.style.display = useMyIP ? 'none' : 'block';\n\t\t}\n\n\t\tfunction resetRecordForm() {\n\t\t\tsetTimeout(() => {\n\t\t\t\tdocument.getElementById('record-type').value = 'A';\n\t\t\t\ttoggleMyIPOption();\n\t\t\t}, 100);\n\t\t}\n\n\t\t// Initialize the form state\n\t\ttoggleMyIPOption();\n\t</script>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }