dev: automated commit - 2025-06-17 16:15:21
This commit is contained in:
parent
73e91db78b
commit
27775e6b29
16 changed files with 1860 additions and 1240 deletions
514
main.go
514
main.go
|
@ -6,8 +6,10 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -18,7 +20,7 @@ import (
|
|||
"github.com/cloudflare/cloudflare-go"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
_ "github.com/mattn/go-sqlite3" // SQLite driver
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
|
@ -30,54 +32,189 @@ var (
|
|||
queries *db.Queries
|
||||
)
|
||||
|
||||
// Simple validation
|
||||
func validateDNSRecord(name, recordType, content string) error {
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return fmt.Errorf("name is required")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(content) == "" {
|
||||
return fmt.Errorf("content is required")
|
||||
}
|
||||
|
||||
// Validate by type
|
||||
switch recordType {
|
||||
case "A":
|
||||
if net.ParseIP(content) == nil {
|
||||
return fmt.Errorf("invalid IP address")
|
||||
}
|
||||
case "CNAME":
|
||||
if !regexp.MustCompile(`^[a-zA-Z0-9\-\.]+$`).MatchString(content) {
|
||||
return fmt.Errorf("invalid domain name")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clean input sanitization
|
||||
func sanitizeInput(input string) string {
|
||||
return strings.TrimSpace(input)
|
||||
}
|
||||
|
||||
// Enhanced error responses
|
||||
func errorResponse(c echo.Context, message string) error {
|
||||
c.Response().WriteHeader(http.StatusBadRequest)
|
||||
return templates.Render(c.Response(), templates.ErrorNotification(message))
|
||||
}
|
||||
|
||||
func successResponse(c echo.Context, message string) error {
|
||||
return templates.Render(c.Response(), templates.SuccessNotification(message))
|
||||
}
|
||||
|
||||
// Improved createDNSRecord
|
||||
func createDNSRecord(zoneID, domain, name, recordType, content string, ttl int, proxied bool) error {
|
||||
if api == nil {
|
||||
return fmt.Errorf("cloudflare API not initialized")
|
||||
}
|
||||
|
||||
// Validate input
|
||||
if err := validateDNSRecord(name, recordType, content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prepare full name
|
||||
fullName := name
|
||||
if name != "@" && !strings.HasSuffix(name, domain) {
|
||||
fullName = name + "." + domain
|
||||
}
|
||||
if name == "@" {
|
||||
fullName = domain
|
||||
}
|
||||
|
||||
// Create record
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
rc := cloudflare.ZoneIdentifier(zoneID)
|
||||
_, err := api.CreateDNSRecord(ctx, rc, cloudflare.CreateDNSRecordParams{
|
||||
Type: recordType,
|
||||
Name: fullName,
|
||||
Content: content,
|
||||
TTL: ttl,
|
||||
Proxied: &proxied,
|
||||
})
|
||||
if err != nil {
|
||||
// Simple error handling
|
||||
if cfErr, ok := err.(*cloudflare.Error); ok {
|
||||
switch cfErr.ErrorCodes[0] {
|
||||
case 10000:
|
||||
return fmt.Errorf("invalid API credentials")
|
||||
case 81044:
|
||||
return fmt.Errorf("record already exists")
|
||||
default:
|
||||
return fmt.Errorf("cloudflare error: %s", cfErr.ErrorMessages[0])
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("failed to create record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateDNSRecord(zoneID, id, name, recordType, content string, ttl int, proxied bool) error {
|
||||
if api == nil {
|
||||
return fmt.Errorf("cloudflare API not initialized")
|
||||
}
|
||||
|
||||
if err := validateDNSRecord(name, recordType, content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
rc := cloudflare.ZoneIdentifier(zoneID)
|
||||
_, err := api.UpdateDNSRecord(ctx, rc, cloudflare.UpdateDNSRecordParams{
|
||||
ID: id,
|
||||
Type: recordType,
|
||||
Name: name,
|
||||
Content: content,
|
||||
TTL: ttl,
|
||||
Proxied: &proxied,
|
||||
})
|
||||
if err != nil {
|
||||
if cfErr, ok := err.(*cloudflare.Error); ok {
|
||||
return fmt.Errorf("cloudflare error: %s", cfErr.ErrorMessages[0])
|
||||
}
|
||||
return fmt.Errorf("failed to update record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteDNSRecord(zoneID, id string) error {
|
||||
if api == nil {
|
||||
return fmt.Errorf("cloudflare API not initialized")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
rc := cloudflare.ZoneIdentifier(zoneID)
|
||||
err := api.DeleteDNSRecord(ctx, rc, id)
|
||||
if err != nil {
|
||||
if cfErr, ok := err.(*cloudflare.Error); ok {
|
||||
return fmt.Errorf("cloudflare error: %s", cfErr.ErrorMessages[0])
|
||||
}
|
||||
return fmt.Errorf("failed to delete record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initDatabase() (*sql.DB, error) {
|
||||
dbPath := os.Getenv("DB_PATH")
|
||||
if dbPath == "" {
|
||||
dbPath = "./ddns.db"
|
||||
}
|
||||
|
||||
log.Printf("Using database path: %s", dbPath)
|
||||
|
||||
sqlDB, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := db.InitSchema(sqlDB); err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize schema: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queries = db.New(sqlDB)
|
||||
return sqlDB, nil
|
||||
}
|
||||
|
||||
func initCloudflare(apiToken, zoneID string) error {
|
||||
if apiToken == "" || zoneID == "" {
|
||||
func initCloudflare(apiToken string) error {
|
||||
if apiToken == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
api, err = cloudflare.NewWithAPIToken(apiToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize Cloudflare API: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func getCurrentIP() (string, error) {
|
||||
resp, err := http.Get("https://api.ipify.org")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get current IP: %w", err)
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
ip, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read IP response: %w", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(ip), nil
|
||||
return strings.TrimSpace(string(ip)), nil
|
||||
}
|
||||
|
||||
func getDNSRecords(zoneID string) ([]templates.DNSRecord, error) {
|
||||
|
@ -85,12 +222,13 @@ func getDNSRecords(zoneID string) ([]templates.DNSRecord, error) {
|
|||
return nil, fmt.Errorf("cloudflare API not initialized")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
rc := cloudflare.ZoneIdentifier(zoneID)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
rc := cloudflare.ZoneIdentifier(zoneID)
|
||||
recs, _, err := api.ListDNSRecords(ctx, rc, cloudflare.ListDNSRecordsParams{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get DNS records: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records []templates.DNSRecord
|
||||
|
@ -109,117 +247,39 @@ func getDNSRecords(zoneID string) ([]templates.DNSRecord, error) {
|
|||
return records, nil
|
||||
}
|
||||
|
||||
func createDNSRecord(zoneID, domain, name, recordType, content string, ttl int, proxied bool) error {
|
||||
if api == nil {
|
||||
return fmt.Errorf("cloudflare API not initialized")
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(name, domain) && name != "@" {
|
||||
name = name + "." + domain
|
||||
}
|
||||
|
||||
if name == "@" {
|
||||
name = domain
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
rc := cloudflare.ZoneIdentifier(zoneID)
|
||||
|
||||
_, err := api.CreateDNSRecord(ctx, rc, cloudflare.CreateDNSRecordParams{
|
||||
Type: recordType,
|
||||
Name: name,
|
||||
Content: content,
|
||||
TTL: ttl,
|
||||
Proxied: &proxied,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create DNS record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateDNSRecord(zoneID, id, name, recordType, content string, ttl int, proxied bool) error {
|
||||
if api == nil {
|
||||
return fmt.Errorf("cloudflare API not initialized")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
rc := cloudflare.ZoneIdentifier(zoneID)
|
||||
|
||||
_, err := api.UpdateDNSRecord(ctx, rc, cloudflare.UpdateDNSRecordParams{
|
||||
ID: id,
|
||||
Type: recordType,
|
||||
Name: name,
|
||||
Content: content,
|
||||
TTL: ttl,
|
||||
Proxied: &proxied,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update DNS record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteDNSRecord(zoneID, id string) error {
|
||||
if api == nil {
|
||||
return fmt.Errorf("cloudflare API not initialized")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
rc := cloudflare.ZoneIdentifier(zoneID)
|
||||
|
||||
err := api.DeleteDNSRecord(ctx, rc, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete DNS record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateAllRecordsWithCurrentIP(zoneID string) error {
|
||||
if api == nil {
|
||||
return fmt.Errorf("cloudflare API not initialized")
|
||||
}
|
||||
|
||||
currentIP, err := getCurrentIP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if currentIP == lastIP {
|
||||
log.Println("IP hasn't changed, no updates needed")
|
||||
return nil
|
||||
}
|
||||
|
||||
lastIP = currentIP
|
||||
|
||||
ctx := context.Background()
|
||||
rc := cloudflare.ZoneIdentifier(zoneID)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
records, _, err := api.ListDNSRecords(ctx, rc, cloudflare.ListDNSRecordsParams{
|
||||
Type: "A",
|
||||
})
|
||||
rc := cloudflare.ZoneIdentifier(zoneID)
|
||||
records, _, err := api.ListDNSRecords(ctx, rc, cloudflare.ListDNSRecordsParams{Type: "A"})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get DNS records: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
if rec.Content != currentIP {
|
||||
proxied := rec.Proxied
|
||||
_, err := api.UpdateDNSRecord(ctx, rc, cloudflare.UpdateDNSRecordParams{
|
||||
ID: rec.ID,
|
||||
Type: rec.Type,
|
||||
Name: rec.Name,
|
||||
Content: currentIP,
|
||||
TTL: rec.TTL,
|
||||
Proxied: proxied,
|
||||
Proxied: rec.Proxied,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Failed to update record %s: %v", rec.Name, err)
|
||||
} else {
|
||||
log.Printf("Updated record %s to %s", rec.Name, currentIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -230,26 +290,22 @@ func updateAllRecordsWithCurrentIP(zoneID string) error {
|
|||
func scheduleUpdates(zoneID, updatePeriod string) error {
|
||||
if jobID != 0 {
|
||||
scheduler.Remove(jobID)
|
||||
log.Println("Scheduled update removed")
|
||||
}
|
||||
|
||||
if updatePeriod == "" {
|
||||
log.Println("Automatic updates disabled")
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
jobID, err = scheduler.AddFunc(updatePeriod, func() {
|
||||
log.Println("Running scheduled IP update")
|
||||
if err := updateAllRecordsWithCurrentIP(zoneID); err != nil {
|
||||
log.Printf("Scheduled update failed: %v", err)
|
||||
}
|
||||
log.Println("Scheduled update completed")
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to schedule updates: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Scheduled IP updates with cron: %s", updatePeriod)
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func getUpdateFrequencies() []templates.UpdateFrequency {
|
||||
|
@ -265,44 +321,42 @@ func getUpdateFrequencies() []templates.UpdateFrequency {
|
|||
}
|
||||
|
||||
func main() {
|
||||
// Initialize database
|
||||
sqlDB, err := initDatabase()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to initialize database: %v", err)
|
||||
log.Fatalf("Database init failed: %v", err)
|
||||
}
|
||||
defer sqlDB.Close()
|
||||
|
||||
// Load config
|
||||
config, err := queries.GetConfig(context.Background())
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to load configuration: %v", err)
|
||||
config = db.Config{
|
||||
Domain: "mz.uy",
|
||||
UpdatePeriod: "0 */6 * * *",
|
||||
}
|
||||
config = db.Config{Domain: "example.com", UpdatePeriod: "0 */6 * * *"}
|
||||
}
|
||||
|
||||
if err := initCloudflare(config.ApiToken, config.ZoneID); err != nil {
|
||||
log.Printf("Warning: Cloudflare initialization failed: %v", err)
|
||||
// Initialize Cloudflare
|
||||
if err := initCloudflare(config.ApiToken); err != nil {
|
||||
log.Printf("Cloudflare init failed: %v", err)
|
||||
}
|
||||
|
||||
// Initialize scheduler
|
||||
scheduler = cron.New()
|
||||
scheduler.Start()
|
||||
defer scheduler.Stop()
|
||||
|
||||
if config.ApiToken != "" && config.ZoneID != "" && config.UpdatePeriod != "" {
|
||||
if err := scheduleUpdates(config.ZoneID, config.UpdatePeriod); err != nil {
|
||||
log.Printf("Warning: Failed to schedule updates: %v", err)
|
||||
}
|
||||
scheduleUpdates(config.ZoneID, config.UpdatePeriod)
|
||||
}
|
||||
|
||||
// Setup Echo
|
||||
e := echo.New()
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
e.Static("/assets", "assets")
|
||||
e.Use(middleware.CORS())
|
||||
|
||||
// Main page
|
||||
// Routes
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
currentIP, _ := getCurrentIP()
|
||||
|
||||
var records []templates.DNSRecord
|
||||
isConfigured := config.ApiToken != "" && config.ZoneID != ""
|
||||
|
||||
|
@ -310,8 +364,8 @@ func main() {
|
|||
records, _ = getDNSRecords(config.ZoneID)
|
||||
}
|
||||
|
||||
component := templates.Index(templates.IndexProps{
|
||||
Title: "mz.uy DNS Manager",
|
||||
return templates.Render(c.Response(), templates.Index(templates.IndexProps{
|
||||
Title: "DNS Manager",
|
||||
IsConfigured: isConfigured,
|
||||
CurrentIP: currentIP,
|
||||
Config: templates.ConfigData{
|
||||
|
@ -322,145 +376,126 @@ func main() {
|
|||
},
|
||||
Records: records,
|
||||
UpdateFreqs: getUpdateFrequencies(),
|
||||
})
|
||||
return templates.Render(c.Response(), component)
|
||||
}))
|
||||
})
|
||||
|
||||
// Refresh current IP
|
||||
e.GET("/refresh-ip", func(c echo.Context) error {
|
||||
ip, err := getCurrentIP()
|
||||
if err != nil {
|
||||
c.Response().Header().Set("HX-Error-Message", "Failed to get current IP")
|
||||
return c.String(http.StatusInternalServerError, "Error")
|
||||
return errorResponse(c, "Failed to get current IP")
|
||||
}
|
||||
return c.String(http.StatusOK, ip)
|
||||
return c.HTML(http.StatusOK, fmt.Sprintf(`<span id="current-ip" class="fw-bold">%s</span>`, ip))
|
||||
})
|
||||
|
||||
// Configuration
|
||||
e.POST("/config", func(c echo.Context) error {
|
||||
apiToken := c.FormValue("api_token")
|
||||
zoneID := c.FormValue("zone_id")
|
||||
domain := c.FormValue("domain")
|
||||
updatePeriod := c.FormValue("update_period")
|
||||
apiToken := sanitizeInput(c.FormValue("api_token"))
|
||||
zoneID := sanitizeInput(c.FormValue("zone_id"))
|
||||
domain := sanitizeInput(c.FormValue("domain"))
|
||||
updatePeriod := sanitizeInput(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")
|
||||
return errorResponse(c, "Please fill all required fields")
|
||||
}
|
||||
|
||||
err := queries.UpsertConfig(context.Background(), db.UpsertConfigParams{
|
||||
// Save config
|
||||
queries.DeleteAllConfig(context.Background())
|
||||
err := queries.InsertConfig(context.Background(), db.InsertConfigParams{
|
||||
ApiToken: apiToken,
|
||||
ZoneID: zoneID,
|
||||
Domain: domain,
|
||||
UpdatePeriod: updatePeriod,
|
||||
})
|
||||
if err != nil {
|
||||
c.Response().Header().Set("HX-Error-Message", "Failed to save configuration")
|
||||
return c.String(http.StatusInternalServerError, "Database error")
|
||||
return errorResponse(c, "Failed to save configuration")
|
||||
}
|
||||
|
||||
// Update global config
|
||||
config.ApiToken = apiToken
|
||||
config.ZoneID = zoneID
|
||||
config.Domain = domain
|
||||
config.UpdatePeriod = updatePeriod
|
||||
|
||||
if err := initCloudflare(config.ApiToken, config.ZoneID); err != nil {
|
||||
c.Response().Header().Set("HX-Error-Message", "Failed to initialize Cloudflare client")
|
||||
return c.String(http.StatusInternalServerError, "API error")
|
||||
}
|
||||
// Reinitialize Cloudflare
|
||||
initCloudflare(apiToken)
|
||||
scheduleUpdates(zoneID, updatePeriod)
|
||||
|
||||
if err := scheduleUpdates(config.ZoneID, config.UpdatePeriod); err != nil {
|
||||
c.Response().Header().Set("HX-Error-Message", "Failed to schedule updates")
|
||||
return c.String(http.StatusInternalServerError, "Scheduler error")
|
||||
}
|
||||
|
||||
c.Response().Header().Set("HX-Success-Message", "Configuration saved successfully")
|
||||
return c.Redirect(http.StatusSeeOther, "/")
|
||||
return templates.Render(c.Response(), templates.ConfigStatus(templates.ConfigData{
|
||||
ZoneID: zoneID,
|
||||
Domain: domain,
|
||||
UpdatePeriod: updatePeriod,
|
||||
ApiToken: apiToken,
|
||||
}))
|
||||
})
|
||||
|
||||
// Create DNS record
|
||||
e.POST("/records", func(c echo.Context) error {
|
||||
if config.ApiToken == "" || config.ZoneID == "" {
|
||||
c.Response().Header().Set("HX-Error-Message", "API not configured")
|
||||
return c.String(http.StatusBadRequest, "Not configured")
|
||||
}
|
||||
e.GET("/config", func(c echo.Context) error {
|
||||
return templates.Render(c.Response(), templates.ConfigModal(templates.ConfigData{
|
||||
ZoneID: config.ZoneID,
|
||||
Domain: config.Domain,
|
||||
UpdatePeriod: config.UpdatePeriod,
|
||||
ApiToken: config.ApiToken,
|
||||
}, getUpdateFrequencies()))
|
||||
})
|
||||
|
||||
name := c.FormValue("name")
|
||||
recordType := c.FormValue("type")
|
||||
content := c.FormValue("content")
|
||||
ttlStr := c.FormValue("ttl")
|
||||
e.GET("/records/new", func(c echo.Context) error {
|
||||
return templates.Render(c.Response(), templates.RecordForm("Add DNS Record", "", config.Domain, templates.DNSRecord{Type: "A", TTL: 1}))
|
||||
})
|
||||
|
||||
e.POST("/records", func(c echo.Context) error {
|
||||
name := sanitizeInput(c.FormValue("name"))
|
||||
recordType := sanitizeInput(c.FormValue("type"))
|
||||
content := sanitizeInput(c.FormValue("content"))
|
||||
ttlStr := sanitizeInput(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")
|
||||
return errorResponse(c, "Failed to get current IP")
|
||||
}
|
||||
content = currentIP
|
||||
}
|
||||
|
||||
if content == "" {
|
||||
c.Response().Header().Set("HX-Error-Message", "Content is required")
|
||||
return c.String(http.StatusBadRequest, "Invalid input")
|
||||
ttl, _ := strconv.Atoi(ttlStr)
|
||||
if ttl == 0 {
|
||||
ttl = 1
|
||||
}
|
||||
|
||||
err = createDNSRecord(config.ZoneID, config.Domain, name, recordType, content, ttl, proxied)
|
||||
if err != nil {
|
||||
c.Response().Header().Set("HX-Error-Message", "Failed to create DNS record")
|
||||
return c.String(http.StatusInternalServerError, "DNS error")
|
||||
if err := createDNSRecord(config.ZoneID, config.Domain, name, recordType, content, ttl, proxied); err != nil {
|
||||
return errorResponse(c, err.Error())
|
||||
}
|
||||
|
||||
c.Response().Header().Set("HX-Success-Message", "DNS record created successfully")
|
||||
|
||||
// Return updated records table
|
||||
// Return updated table
|
||||
records, _ := getDNSRecords(config.ZoneID)
|
||||
currentIP, _ := getCurrentIP()
|
||||
component := templates.DNSRecordsTable(records, currentIP)
|
||||
return templates.Render(c.Response(), component)
|
||||
notification := templates.SuccessNotification("DNS record created")
|
||||
table := templates.DNSRecordsTable(records, currentIP)
|
||||
return templates.RenderMultiple(c.Response().Writer, notification, table)
|
||||
})
|
||||
|
||||
// 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")
|
||||
name := sanitizeInput(c.FormValue("name"))
|
||||
recordType := sanitizeInput(c.FormValue("type"))
|
||||
content := sanitizeInput(c.FormValue("content"))
|
||||
ttlStr := sanitizeInput(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")
|
||||
return errorResponse(c, "Failed to get current IP")
|
||||
}
|
||||
content = currentIP
|
||||
}
|
||||
|
||||
// Convert name to full domain name if needed
|
||||
ttl, _ := strconv.Atoi(ttlStr)
|
||||
if ttl == 0 {
|
||||
ttl = 1
|
||||
}
|
||||
|
||||
// Convert name to full domain
|
||||
fullName := name
|
||||
if name != "@" && !strings.HasSuffix(name, config.Domain) {
|
||||
fullName = name + "." + config.Domain
|
||||
|
@ -469,51 +504,36 @@ func main() {
|
|||
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")
|
||||
if err := updateDNSRecord(config.ZoneID, id, fullName, recordType, content, ttl, proxied); err != nil {
|
||||
return errorResponse(c, err.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)
|
||||
notification := templates.SuccessNotification("DNS record updated")
|
||||
table := templates.DNSRecordsTable(records, currentIP)
|
||||
return templates.RenderMultiple(c.Response().Writer, notification, table)
|
||||
})
|
||||
|
||||
// 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")
|
||||
|
||||
if err := deleteDNSRecord(config.ZoneID, id); err != nil {
|
||||
return errorResponse(c, "Failed to delete record")
|
||||
}
|
||||
|
||||
c.Response().Header().Set("HX-Success-Message", "DNS record deleted successfully")
|
||||
return c.String(http.StatusOK, "")
|
||||
records, _ := getDNSRecords(config.ZoneID)
|
||||
currentIP, _ := getCurrentIP()
|
||||
notification := templates.SuccessNotification("DNS record deleted")
|
||||
table := templates.DNSRecordsTable(records, currentIP)
|
||||
return templates.RenderMultiple(c.Response().Writer, notification, table)
|
||||
})
|
||||
|
||||
// Edit record form
|
||||
e.GET("/edit-record/:id", func(c echo.Context) error {
|
||||
if config.ApiToken == "" || config.ZoneID == "" {
|
||||
c.Response().Header().Set("HX-Error-Message", "API not configured")
|
||||
return c.String(http.StatusBadRequest, "Not configured")
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
records, err := getDNSRecords(config.ZoneID)
|
||||
if err != nil {
|
||||
c.Response().Header().Set("HX-Error-Message", "Failed to load DNS records")
|
||||
return c.String(http.StatusInternalServerError, "DNS error")
|
||||
return errorResponse(c, "Failed to load records")
|
||||
}
|
||||
|
||||
var record templates.DNSRecord
|
||||
|
@ -525,36 +545,24 @@ func main() {
|
|||
}
|
||||
|
||||
if record.ID == "" {
|
||||
c.Response().Header().Set("HX-Error-Message", "Record not found")
|
||||
return c.String(http.StatusNotFound, "Not found")
|
||||
return errorResponse(c, "Record not found")
|
||||
}
|
||||
|
||||
component := templates.RecordForm("Edit DNS Record", id, config.Domain, record)
|
||||
return templates.Render(c.Response(), component)
|
||||
return templates.Render(c.Response(), templates.RecordForm("Edit DNS Record", id, config.Domain, record))
|
||||
})
|
||||
|
||||
// Update all records with current IP
|
||||
e.POST("/update-all-records", func(c echo.Context) error {
|
||||
if config.ApiToken == "" || config.ZoneID == "" {
|
||||
c.Response().Header().Set("HX-Error-Message", "API not configured")
|
||||
return c.String(http.StatusBadRequest, "Not configured")
|
||||
if err := updateAllRecordsWithCurrentIP(config.ZoneID); err != nil {
|
||||
return errorResponse(c, "Failed to update records")
|
||||
}
|
||||
|
||||
err := updateAllRecordsWithCurrentIP(config.ZoneID)
|
||||
if err != nil {
|
||||
c.Response().Header().Set("HX-Error-Message", "Failed to update records")
|
||||
return c.String(http.StatusInternalServerError, "Update error")
|
||||
}
|
||||
|
||||
c.Response().Header().Set("HX-Success-Message", "All A records updated with current IP")
|
||||
|
||||
// Return updated records table
|
||||
records, _ := getDNSRecords(config.ZoneID)
|
||||
currentIP, _ := getCurrentIP()
|
||||
component := templates.DNSRecordsTable(records, currentIP)
|
||||
return templates.Render(c.Response(), component)
|
||||
notification := templates.SuccessNotification("All A records updated")
|
||||
table := templates.DNSRecordsTable(records, currentIP)
|
||||
return templates.RenderMultiple(c.Response().Writer, notification, table)
|
||||
})
|
||||
|
||||
log.Println("Starting server on http://localhost:3000")
|
||||
log.Println("Starting server on :3000")
|
||||
log.Fatal(e.Start(":3000"))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue