dev: automated commit - 2025-06-16 09:37:04
This commit is contained in:
parent
682f25edcd
commit
d71a7e37c5
7 changed files with 1513 additions and 430 deletions
401
main.go
401
main.go
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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 -->
|
</td>
|
||||||
<div class="toast-container"></div>
|
<td>
|
||||||
<script src="/assets/js/app.js"></script>
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue