corrige bug com registro txt e tradução
This commit is contained in:
@@ -23,8 +23,8 @@ type Validator struct {
|
|||||||
|
|
||||||
func NewValidator() (*Validator, error) {
|
func NewValidator() (*Validator, error) {
|
||||||
v := validator.New()
|
v := validator.New()
|
||||||
if err := v.RegisterValidation("dns_fqdn", func(fl validator.FieldLevel) bool {
|
if err := v.RegisterValidation("dns_name", func(fl validator.FieldLevel) bool {
|
||||||
return IsFQDN(fl.Field().String())
|
return IsDNSName(fl.Field().String())
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -41,13 +41,13 @@ func (v *Validator) ValidateRRSet(name, recordType string, ttl uint64, contents
|
|||||||
recordType = strings.ToUpper(strings.TrimSpace(recordType))
|
recordType = strings.ToUpper(strings.TrimSpace(recordType))
|
||||||
|
|
||||||
if ttl == 0 || ttl > 1<<32-1 {
|
if ttl == 0 || ttl > 1<<32-1 {
|
||||||
return pdns.RRSet{}, fmt.Errorf("ttl must be between 1 and 4294967295")
|
return pdns.RRSet{}, fmt.Errorf("ttl deve estar entre 1 e 4294967295")
|
||||||
}
|
}
|
||||||
if err := v.validate.Var(name, "required,dns_fqdn"); err != nil {
|
if err := v.validate.Var(name, "required,dns_name"); err != nil {
|
||||||
return pdns.RRSet{}, fmt.Errorf("record name must be a fully qualified domain name")
|
return pdns.RRSet{}, fmt.Errorf("o nome do registro precisa ser um domínio totalmente qualificado")
|
||||||
}
|
}
|
||||||
if !supported(recordType) {
|
if !supported(recordType) {
|
||||||
return pdns.RRSet{}, fmt.Errorf("unsupported record type %q", recordType)
|
return pdns.RRSet{}, fmt.Errorf("tipo de registro %q não suportado", recordType)
|
||||||
}
|
}
|
||||||
|
|
||||||
records := make([]pdns.Record, 0, len(contents))
|
records := make([]pdns.Record, 0, len(contents))
|
||||||
@@ -62,7 +62,7 @@ func (v *Validator) ValidateRRSet(name, recordType string, ttl uint64, contents
|
|||||||
records = append(records, pdns.Record{Content: content})
|
records = append(records, pdns.Record{Content: content})
|
||||||
}
|
}
|
||||||
if len(records) == 0 {
|
if len(records) == 0 {
|
||||||
return pdns.RRSet{}, fmt.Errorf("at least one record value is required")
|
return pdns.RRSet{}, fmt.Errorf("é obrigatório ao menos um valor de registro")
|
||||||
}
|
}
|
||||||
|
|
||||||
return pdns.RRSet{
|
return pdns.RRSet{
|
||||||
@@ -81,7 +81,17 @@ func EnsureTrailingDot(value string) string {
|
|||||||
return value + "."
|
return value + "."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsFQDN reports whether value is an absolute hostname-style DNS name.
|
||||||
func IsFQDN(value string) bool {
|
func IsFQDN(value string) bool {
|
||||||
|
return isDomainName(value, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDNSName reports whether value is an absolute DNS owner name.
|
||||||
|
func IsDNSName(value string) bool {
|
||||||
|
return isDomainName(value, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDomainName(value string, allowUnderscore bool) bool {
|
||||||
value = strings.TrimSpace(value)
|
value = strings.TrimSpace(value)
|
||||||
if value == "." || value == "" || !strings.HasSuffix(value, ".") || len(value) > 253 {
|
if value == "." || value == "" || !strings.HasSuffix(value, ".") || len(value) > 253 {
|
||||||
return false
|
return false
|
||||||
@@ -99,7 +109,7 @@ func IsFQDN(value string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, r := range label {
|
for _, r := range label {
|
||||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' {
|
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || (allowUnderscore && r == '_') {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -113,18 +123,18 @@ func validateContent(recordType, content string) error {
|
|||||||
case "A":
|
case "A":
|
||||||
addr, err := netip.ParseAddr(content)
|
addr, err := netip.ParseAddr(content)
|
||||||
if err != nil || !addr.Is4() {
|
if err != nil || !addr.Is4() {
|
||||||
return fmt.Errorf("A record content must be an IPv4 address")
|
return fmt.Errorf("o conteúdo do registro A precisa ser um endereco IPv4")
|
||||||
}
|
}
|
||||||
case "AAAA":
|
case "AAAA":
|
||||||
addr, err := netip.ParseAddr(content)
|
addr, err := netip.ParseAddr(content)
|
||||||
if err != nil || !addr.Is6() {
|
if err != nil || !addr.Is6() {
|
||||||
return fmt.Errorf("AAAA record content must be an IPv6 address")
|
return fmt.Errorf("o conteúdo do registro AAAA precisa ser um endereco IPv6")
|
||||||
}
|
}
|
||||||
case "CAA":
|
case "CAA":
|
||||||
return validateCAA(content)
|
return validateCAA(content)
|
||||||
case "CNAME", "NS":
|
case "CNAME", "NS":
|
||||||
if !IsFQDN(content) {
|
if !IsFQDN(content) {
|
||||||
return fmt.Errorf("%s record content must be a fully qualified domain name", recordType)
|
return fmt.Errorf("o conteúdo do registro %s precisa ser um domínio totalmente qualificado", recordType)
|
||||||
}
|
}
|
||||||
case "MX":
|
case "MX":
|
||||||
return validateMX(content)
|
return validateMX(content)
|
||||||
@@ -134,7 +144,7 @@ func validateContent(recordType, content string) error {
|
|||||||
return validateSRV(content)
|
return validateSRV(content)
|
||||||
case "TXT":
|
case "TXT":
|
||||||
if !txtValuePattern.MatchString(content) {
|
if !txtValuePattern.MatchString(content) {
|
||||||
return fmt.Errorf("TXT record content must be one or more quoted strings")
|
return fmt.Errorf("o conteúdo do registro TXT precisa ser uma ou mais strings entre aspas")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -143,13 +153,13 @@ func validateContent(recordType, content string) error {
|
|||||||
func validateMX(content string) error {
|
func validateMX(content string) error {
|
||||||
fields := strings.Fields(content)
|
fields := strings.Fields(content)
|
||||||
if len(fields) != 2 {
|
if len(fields) != 2 {
|
||||||
return fmt.Errorf("MX record content must be: priority target")
|
return fmt.Errorf("o conteúdo do registro MX precisa ser: prioridade destino")
|
||||||
}
|
}
|
||||||
if _, err := parseUint(fields[0], 16); err != nil {
|
if _, err := parseUint(fields[0], 16); err != nil {
|
||||||
return fmt.Errorf("MX priority must be between 0 and 65535")
|
return fmt.Errorf("a prioridade MX deve estar entre 0 e 65535")
|
||||||
}
|
}
|
||||||
if !IsFQDN(fields[1]) {
|
if !IsFQDN(fields[1]) {
|
||||||
return fmt.Errorf("MX target must be a fully qualified domain name")
|
return fmt.Errorf("o destino MX precisa ser um domínio totalmente qualificado")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -157,16 +167,16 @@ func validateMX(content string) error {
|
|||||||
func validateSRV(content string) error {
|
func validateSRV(content string) error {
|
||||||
fields := strings.Fields(content)
|
fields := strings.Fields(content)
|
||||||
if len(fields) != 4 {
|
if len(fields) != 4 {
|
||||||
return fmt.Errorf("SRV record content must be: priority weight port target")
|
return fmt.Errorf("o conteúdo do registro SRV precisa ser: prioridade peso porta destino")
|
||||||
}
|
}
|
||||||
for i, label := range []string{"priority", "weight", "port"} {
|
for i, label := range []string{"prioridade", "peso", "porta"} {
|
||||||
if _, err := parseUint(fields[i], 16); err != nil {
|
if _, err := parseUint(fields[i], 16); err != nil {
|
||||||
return fmt.Errorf("SRV %s must be between 0 and 65535", label)
|
return fmt.Errorf("o campo SRV %s deve estar entre 0 e 65535", label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
target := strings.TrimSpace(fields[3])
|
target := strings.TrimSpace(fields[3])
|
||||||
if target != "." && !IsFQDN(target) {
|
if target != "." && !IsFQDN(target) {
|
||||||
return fmt.Errorf("SRV target must be a fully qualified domain name or .")
|
return fmt.Errorf("o destino SRV precisa ser um domínio totalmente qualificado ou .")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -174,17 +184,17 @@ func validateSRV(content string) error {
|
|||||||
func validateSOA(content string) error {
|
func validateSOA(content string) error {
|
||||||
fields := strings.Fields(content)
|
fields := strings.Fields(content)
|
||||||
if len(fields) != 7 {
|
if len(fields) != 7 {
|
||||||
return fmt.Errorf("SOA record content must be: primary hostmaster serial refresh retry expire minimum")
|
return fmt.Errorf("o conteúdo do registro SOA precisa ser: primario hostmaster serial atualizacao nova-tentativa expiracao minimo")
|
||||||
}
|
}
|
||||||
if !IsFQDN(fields[0]) {
|
if !IsFQDN(fields[0]) {
|
||||||
return fmt.Errorf("SOA primary nameserver must be a fully qualified domain name")
|
return fmt.Errorf("o servidor de nomes primario do SOA precisa ser um domínio totalmente qualificado")
|
||||||
}
|
}
|
||||||
if !IsFQDN(fields[1]) {
|
if !IsFQDN(fields[1]) {
|
||||||
return fmt.Errorf("SOA hostmaster must be a fully qualified domain name")
|
return fmt.Errorf("o hostmaster do SOA precisa ser um domínio totalmente qualificado")
|
||||||
}
|
}
|
||||||
for i, label := range []string{"serial", "refresh", "retry", "expire", "minimum"} {
|
for i, label := range []string{"serial", "atualizacao", "nova-tentativa", "expiracao", "minimo"} {
|
||||||
if _, err := parseUint(fields[i+2], 32); err != nil {
|
if _, err := parseUint(fields[i+2], 32); err != nil {
|
||||||
return fmt.Errorf("SOA %s must be between 0 and 4294967295", label)
|
return fmt.Errorf("SOA %s deve estar entre 0 e 4294967295", label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -193,18 +203,18 @@ func validateSOA(content string) error {
|
|||||||
func validateCAA(content string) error {
|
func validateCAA(content string) error {
|
||||||
fields := strings.Fields(content)
|
fields := strings.Fields(content)
|
||||||
if len(fields) < 3 {
|
if len(fields) < 3 {
|
||||||
return fmt.Errorf("CAA record content must be: flags tag \"value\"")
|
return fmt.Errorf("o conteúdo do registro CAA precisa ser: flags tag \"valor\"")
|
||||||
}
|
}
|
||||||
flags, err := parseUint(fields[0], 8)
|
flags, err := parseUint(fields[0], 8)
|
||||||
if err != nil || flags > 255 {
|
if err != nil || flags > 255 {
|
||||||
return fmt.Errorf("CAA flags must be between 0 and 255")
|
return fmt.Errorf("o campo flags do CAA deve estar entre 0 e 255")
|
||||||
}
|
}
|
||||||
if !caaTagPattern.MatchString(fields[1]) {
|
if !caaTagPattern.MatchString(fields[1]) {
|
||||||
return fmt.Errorf("CAA tag must start with a letter and contain only letters, numbers, and hyphens")
|
return fmt.Errorf("a tag CAA deve começar com uma letra e conter apenas letras, numeros e hífens")
|
||||||
}
|
}
|
||||||
value := strings.Join(fields[2:], " ")
|
value := strings.Join(fields[2:], " ")
|
||||||
if !txtValuePattern.MatchString(value) {
|
if !txtValuePattern.MatchString(value) {
|
||||||
return fmt.Errorf("CAA value must be quoted")
|
return fmt.Errorf("o valor CAA deve estar entre aspas")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ func TestValidateRRSetAcceptsSupportedRecords(t *testing.T) {
|
|||||||
{"SOA", "ns1.example.org. hostmaster.example.org. 2026010101 3600 600 604800 300"},
|
{"SOA", "ns1.example.org. hostmaster.example.org. 2026010101 3600 600 604800 300"},
|
||||||
{"SRV", "10 20 443 service.example.org."},
|
{"SRV", "10 20 443 service.example.org."},
|
||||||
{"TXT", `"v=spf1 -all"`},
|
{"TXT", `"v=spf1 -all"`},
|
||||||
|
{"TXT", `"v=DMARC1; p=reject; rua=mailto:postmaster@example.org; ruf=mailto:postmaster@example.org"`},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
@@ -56,6 +57,68 @@ func TestValidateRRSetRejectsInvalidRecords(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateRRSetAcceptsUnderscoresInOwnerNames(t *testing.T) {
|
||||||
|
v := newTestValidator(t)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
recordType string
|
||||||
|
content string
|
||||||
|
wantName string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "_dmarc.example.org",
|
||||||
|
recordType: "TXT",
|
||||||
|
content: `"v=DMARC1; p=reject; rua=mailto:postmaster@example.org; ruf=mailto:postmaster@example.org"`,
|
||||||
|
wantName: "_dmarc.example.org.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_sip._tcp.example.org",
|
||||||
|
recordType: "SRV",
|
||||||
|
content: "10 20 5060 sip.example.org.",
|
||||||
|
wantName: "_sip._tcp.example.org.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
rrset, err := v.ValidateRRSet(tc.name, tc.recordType, 300, []string{tc.content})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ValidateRRSet returned error: %v", err)
|
||||||
|
}
|
||||||
|
if rrset.Name != tc.wantName {
|
||||||
|
t.Fatalf("unexpected normalized name: %q", rrset.Name)
|
||||||
|
}
|
||||||
|
if rrset.Records[0].Content != tc.content {
|
||||||
|
t.Fatalf("unexpected content: %q", rrset.Records[0].Content)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateRRSetRejectsUnderscoresInHostnameTargets(t *testing.T) {
|
||||||
|
v := newTestValidator(t)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
recordType string
|
||||||
|
content string
|
||||||
|
}{
|
||||||
|
{"CNAME", "_target.example.org."},
|
||||||
|
{"MX", "10 _mail.example.org."},
|
||||||
|
{"NS", "_ns.example.org."},
|
||||||
|
{"SOA", "_ns.example.org. hostmaster.example.org. 2026010101 3600 600 604800 300"},
|
||||||
|
{"SRV", "10 20 5060 _sip.example.org."},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.recordType, func(t *testing.T) {
|
||||||
|
if _, err := v.ValidateRRSet("www.example.org.", tc.recordType, 300, []string{tc.content}); err == nil {
|
||||||
|
t.Fatal("expected validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidateRRSetRequiresTTL(t *testing.T) {
|
func TestValidateRRSetRequiresTTL(t *testing.T) {
|
||||||
v := newTestValidator(t)
|
v := newTestValidator(t)
|
||||||
|
|
||||||
|
|||||||
@@ -159,14 +159,14 @@ func (s *Server) healthz(w http.ResponseWriter, _ *http.Request) {
|
|||||||
|
|
||||||
func (s *Server) dashboard(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) dashboard(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/" {
|
if r.URL.Path != "/" {
|
||||||
http.NotFound(w, r)
|
http.Error(w, "pagina nao encontrada", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
server, serverErr := s.client.GetServer(r.Context())
|
server, serverErr := s.client.GetServer(r.Context())
|
||||||
zones, zonesErr := s.client.ListZones(r.Context())
|
zones, zonesErr := s.client.ListZones(r.Context())
|
||||||
data := pageData{
|
data := pageData{
|
||||||
Title: "Dashboard",
|
Title: "Painel",
|
||||||
Server: server,
|
Server: server,
|
||||||
Zones: zones,
|
Zones: zones,
|
||||||
Error: firstNonEmpty(errorText(serverErr), errorText(zonesErr)),
|
Error: firstNonEmpty(errorText(serverErr), errorText(zonesErr)),
|
||||||
@@ -185,7 +185,7 @@ func (s *Server) login(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data := pageData{
|
data := pageData{
|
||||||
Title: "Login",
|
Title: "Entrar",
|
||||||
Error: r.URL.Query().Get("error"),
|
Error: r.URL.Query().Get("error"),
|
||||||
Next: safeRedirectPath(r.URL.Query().Get("next")),
|
Next: safeRedirectPath(r.URL.Query().Get("next")),
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ func (s *Server) loginPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Redirect(w, r, "/login?error="+url.QueryEscape("invalid form data"), http.StatusSeeOther)
|
http.Redirect(w, r, "/login?error="+url.QueryEscape("dados do formulario invalidos"), http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,18 +207,18 @@ func (s *Server) loginPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
allowed, err := s.auth.Authenticate(r.Context(), username, password)
|
allowed, err := s.auth.Authenticate(r.Context(), username, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Printf("authentication failed for %q: %v", username, err)
|
s.logger.Printf("authentication failed for %q: %v", username, err)
|
||||||
http.Redirect(w, r, "/login?error="+url.QueryEscape("authentication backend failed"), http.StatusSeeOther)
|
http.Redirect(w, r, "/login?error="+url.QueryEscape("falha no servico de autenticacao"), http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !allowed {
|
if !allowed {
|
||||||
http.Redirect(w, r, "/login?error="+url.QueryEscape("invalid username or password"), http.StatusSeeOther)
|
http.Redirect(w, r, "/login?error="+url.QueryEscape("usuario ou senha invalidos"), http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := s.createSession(username)
|
token, err := s.createSession(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Printf("create session: %v", err)
|
s.logger.Printf("create session: %v", err)
|
||||||
http.Error(w, "session creation failed", http.StatusInternalServerError)
|
http.Error(w, "falha ao criar sessao", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
@@ -255,7 +255,7 @@ func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (s *Server) listZones(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) listZones(w http.ResponseWriter, r *http.Request) {
|
||||||
zones, err := s.client.ListZones(r.Context())
|
zones, err := s.client.ListZones(r.Context())
|
||||||
data := pageData{
|
data := pageData{
|
||||||
Title: "Zones",
|
Title: "Zonas",
|
||||||
Zones: zones,
|
Zones: zones,
|
||||||
Error: firstNonEmpty(r.URL.Query().Get("error"), errorText(err)),
|
Error: firstNonEmpty(r.URL.Query().Get("error"), errorText(err)),
|
||||||
}
|
}
|
||||||
@@ -264,30 +264,30 @@ func (s *Server) listZones(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func (s *Server) createZone(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) createZone(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
s.redirectZonesError(w, r, "invalid form data")
|
s.redirectZonesError(w, r, "dados do formulario invalidos")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
zoneName := dnsrecord.EnsureTrailingDot(r.FormValue("name"))
|
zoneName := dnsrecord.EnsureTrailingDot(r.FormValue("name"))
|
||||||
if !dnsrecord.IsFQDN(zoneName) {
|
if !dnsrecord.IsFQDN(zoneName) {
|
||||||
s.redirectZonesError(w, r, "zone name must be a fully qualified domain name")
|
s.redirectZonesError(w, r, "o nome da zona deve ser um nome de dominio totalmente qualificado")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
kind := strings.TrimSpace(r.FormValue("kind"))
|
kind := strings.TrimSpace(r.FormValue("kind"))
|
||||||
if !validZoneKind(kind) {
|
if !validZoneKind(kind) {
|
||||||
s.redirectZonesError(w, r, "zone kind must be Native, Master, or Slave")
|
s.redirectZonesError(w, r, "o tipo da zona deve ser Native, Master ou Slave")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nameservers, err := parseFQDNLines(r.FormValue("nameservers"), "nameserver")
|
nameservers, err := parseFQDNLines(r.FormValue("nameservers"), "servidor de nomes")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.redirectZonesError(w, r, err.Error())
|
s.redirectZonesError(w, r, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
masters := parseLines(r.FormValue("masters"))
|
masters := parseLines(r.FormValue("masters"))
|
||||||
if kind == "Slave" && len(masters) == 0 {
|
if kind == "Slave" && len(masters) == 0 {
|
||||||
s.redirectZonesError(w, r, "slave zones require at least one master address")
|
s.redirectZonesError(w, r, "zonas Slave exigem ao menos um endereco de servidor mestre")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +317,7 @@ func (s *Server) showZone(w http.ResponseWriter, r *http.Request) {
|
|||||||
zoneID := r.PathValue("zoneID")
|
zoneID := r.PathValue("zoneID")
|
||||||
zone, err := s.client.GetZone(r.Context(), zoneID)
|
zone, err := s.client.GetZone(r.Context(), zoneID)
|
||||||
data := pageData{
|
data := pageData{
|
||||||
Title: "Zone " + zoneID,
|
Title: "Zona " + zoneID,
|
||||||
ZoneID: zoneID,
|
ZoneID: zoneID,
|
||||||
Zone: zone,
|
Zone: zone,
|
||||||
Error: firstNonEmpty(r.URL.Query().Get("error"), errorText(err)),
|
Error: firstNonEmpty(r.URL.Query().Get("error"), errorText(err)),
|
||||||
@@ -329,7 +329,7 @@ func (s *Server) newRRSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
zoneID := r.PathValue("zoneID")
|
zoneID := r.PathValue("zoneID")
|
||||||
zone, err := s.client.GetZone(r.Context(), zoneID)
|
zone, err := s.client.GetZone(r.Context(), zoneID)
|
||||||
data := pageData{
|
data := pageData{
|
||||||
Title: "Add record",
|
Title: "Adicionar registro",
|
||||||
ZoneID: zoneID,
|
ZoneID: zoneID,
|
||||||
Zone: zone,
|
Zone: zone,
|
||||||
Error: firstNonEmpty(r.URL.Query().Get("error"), errorText(err)),
|
Error: firstNonEmpty(r.URL.Query().Get("error"), errorText(err)),
|
||||||
@@ -337,8 +337,8 @@ func (s *Server) newRRSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
RecordForm: recordForm{
|
RecordForm: recordForm{
|
||||||
Type: "A",
|
Type: "A",
|
||||||
TTL: 300,
|
TTL: 300,
|
||||||
Title: "Add record",
|
Title: "Adicionar registro",
|
||||||
SubmitLabel: "Create record",
|
SubmitLabel: "Criar registro",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
s.render(w, r, "record_form.html", data)
|
s.render(w, r, "record_form.html", data)
|
||||||
@@ -356,7 +356,7 @@ func (s *Server) editRRSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
rrset, ok := findRRSet(zone, name, recordType)
|
rrset, ok := findRRSet(zone, name, recordType)
|
||||||
if !ok {
|
if !ok {
|
||||||
s.redirectZoneError(w, r, zoneID, "record not found")
|
s.redirectZoneError(w, r, zoneID, "registro nao encontrado")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,11 +366,11 @@ func (s *Server) editRRSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
TTL: rrset.TTL,
|
TTL: rrset.TTL,
|
||||||
Records: recordValues(rrset),
|
Records: recordValues(rrset),
|
||||||
IsEdit: true,
|
IsEdit: true,
|
||||||
Title: "Edit record",
|
Title: "Editar registro",
|
||||||
SubmitLabel: "Save record",
|
SubmitLabel: "Salvar registro",
|
||||||
}
|
}
|
||||||
data := pageData{
|
data := pageData{
|
||||||
Title: "Edit record",
|
Title: "Editar registro",
|
||||||
ZoneID: zoneID,
|
ZoneID: zoneID,
|
||||||
Zone: zone,
|
Zone: zone,
|
||||||
Error: r.URL.Query().Get("error"),
|
Error: r.URL.Query().Get("error"),
|
||||||
@@ -383,13 +383,13 @@ func (s *Server) editRRSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (s *Server) saveRRSet(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) saveRRSet(w http.ResponseWriter, r *http.Request) {
|
||||||
zoneID := r.PathValue("zoneID")
|
zoneID := r.PathValue("zoneID")
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
s.redirectZoneError(w, r, zoneID, "invalid form data")
|
s.redirectZoneError(w, r, zoneID, "dados do formulario invalidos")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ttl, err := strconv.ParseUint(strings.TrimSpace(r.FormValue("ttl")), 10, 32)
|
ttl, err := strconv.ParseUint(strings.TrimSpace(r.FormValue("ttl")), 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.redirectZoneError(w, r, zoneID, "ttl must be a positive integer")
|
s.redirectZoneError(w, r, zoneID, "ttl deve ser um numero inteiro positivo")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,18 +412,18 @@ func (s *Server) saveEditedRRSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
name := r.URL.Query().Get("name")
|
name := r.URL.Query().Get("name")
|
||||||
recordType := strings.ToUpper(strings.TrimSpace(r.URL.Query().Get("type")))
|
recordType := strings.ToUpper(strings.TrimSpace(r.URL.Query().Get("type")))
|
||||||
if name == "" || recordType == "" {
|
if name == "" || recordType == "" {
|
||||||
s.redirectZoneError(w, r, zoneID, "record identity is required")
|
s.redirectZoneError(w, r, zoneID, "a identidade do registro e obrigatoria")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
s.redirectZoneError(w, r, zoneID, "invalid form data")
|
s.redirectZoneError(w, r, zoneID, "dados do formulario invalidos")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ttl, err := strconv.ParseUint(strings.TrimSpace(r.FormValue("ttl")), 10, 32)
|
ttl, err := strconv.ParseUint(strings.TrimSpace(r.FormValue("ttl")), 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.redirectZoneError(w, r, zoneID, "ttl must be a positive integer")
|
s.redirectZoneError(w, r, zoneID, "ttl deve ser um numero inteiro positivo")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,18 +443,18 @@ func (s *Server) saveEditedRRSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (s *Server) deleteRRSet(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) deleteRRSet(w http.ResponseWriter, r *http.Request) {
|
||||||
zoneID := r.PathValue("zoneID")
|
zoneID := r.PathValue("zoneID")
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
s.redirectZoneError(w, r, zoneID, "invalid form data")
|
s.redirectZoneError(w, r, zoneID, "dados do formulario invalidos")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
name := dnsrecord.EnsureTrailingDot(r.FormValue("name"))
|
name := dnsrecord.EnsureTrailingDot(r.FormValue("name"))
|
||||||
recordType := strings.ToUpper(strings.TrimSpace(r.FormValue("type")))
|
recordType := strings.ToUpper(strings.TrimSpace(r.FormValue("type")))
|
||||||
if name == "." || recordType == "" {
|
if name == "." || recordType == "" {
|
||||||
s.redirectZoneError(w, r, zoneID, "record name and type are required")
|
s.redirectZoneError(w, r, zoneID, "nome e tipo do registro sao obrigatorios")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if isSOA(recordType) {
|
if isSOA(recordType) {
|
||||||
s.redirectZoneError(w, r, zoneID, "SOA records are required for zones and cannot be deleted")
|
s.redirectZoneError(w, r, zoneID, "registros SOA sao obrigatorios para zonas e nao podem ser excluidos")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,7 +475,7 @@ func (s *Server) render(w http.ResponseWriter, r *http.Request, name string, dat
|
|||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
tmpl, ok := s.templates[name]
|
tmpl, ok := s.templates[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(w, "template not found", http.StatusInternalServerError)
|
http.Error(w, "template nao encontrado", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := tmpl.ExecuteTemplate(w, name, data); err != nil {
|
if err := tmpl.ExecuteTemplate(w, name, data); err != nil {
|
||||||
@@ -507,7 +507,7 @@ func (s *Server) withSessionAuth(next http.Handler) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if isUnsafeMethod(r.Method) && !validCSRFToken(r, sess.CSRFToken) {
|
if isUnsafeMethod(r.Method) && !validCSRFToken(r, sess.CSRFToken) {
|
||||||
http.Error(w, "invalid CSRF token", http.StatusForbidden)
|
http.Error(w, "token CSRF invalido", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
@@ -664,7 +664,7 @@ func parseFQDNLines(raw, label string) ([]string, error) {
|
|||||||
for i, value := range values {
|
for i, value := range values {
|
||||||
values[i] = dnsrecord.EnsureTrailingDot(value)
|
values[i] = dnsrecord.EnsureTrailingDot(value)
|
||||||
if !dnsrecord.IsFQDN(values[i]) {
|
if !dnsrecord.IsFQDN(values[i]) {
|
||||||
return nil, fmt.Errorf("%s %q must be a fully qualified domain name", label, value)
|
return nil, fmt.Errorf("%s %q deve ser um nome de dominio totalmente qualificado", label, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return values, nil
|
return values, nil
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{{ define "layout" }}
|
{{ define "layout" }}
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" data-bs-theme="light">
|
<html lang="pt-BR" data-bs-theme="light">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -15,16 +15,16 @@
|
|||||||
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
|
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
|
||||||
<a href="/">AehooDNS</a>
|
<a href="/">AehooDNS</a>
|
||||||
</h1>
|
</h1>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-menu" aria-controls="navbar-menu" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-menu" aria-controls="navbar-menu" aria-expanded="false" aria-label="Alternar navegacao">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbar-menu">
|
<div class="collapse navbar-collapse" id="navbar-menu">
|
||||||
<div class="navbar-nav">
|
<div class="navbar-nav">
|
||||||
<a class="nav-link" href="/">
|
<a class="nav-link" href="/">
|
||||||
<span class="nav-link-title">Dashboard</span>
|
<span class="nav-link-title">Painel</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="nav-link" href="/zones">
|
<a class="nav-link" href="/zones">
|
||||||
<span class="nav-link-title">Zones</span>
|
<span class="nav-link-title">Zonas</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{{ if .AuthEnabled }}
|
{{ if .AuthEnabled }}
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<form method="post" action="/logout" class="nav-item">
|
<form method="post" action="/logout" class="nav-item">
|
||||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||||
<button class="nav-link btn btn-link px-0" type="submit">
|
<button class="nav-link btn btn-link px-0" type="submit">
|
||||||
<span class="nav-link-title">Logout</span>
|
<span class="nav-link-title">Sair</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="page-pretitle">PowerDNS</div>
|
<div class="page-pretitle">PowerDNS</div>
|
||||||
<h2 class="page-title">Dashboard</h2>
|
<h2 class="page-title">Painel</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<div class="col-sm-6 col-lg-3">
|
<div class="col-sm-6 col-lg-3">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="subheader">Server ID</div>
|
<div class="subheader">ID do servidor</div>
|
||||||
<div class="h2 mb-0 text-truncate">{{ .Server.ID }}</div>
|
<div class="h2 mb-0 text-truncate">{{ .Server.ID }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -20,15 +20,17 @@
|
|||||||
<div class="col-sm-6 col-lg-3">
|
<div class="col-sm-6 col-lg-3">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="subheader">Daemon</div>
|
<div class="subheader">Serviço</div>
|
||||||
<div class="h2 mb-0 text-truncate">{{ .Server.DaemonType }}</div>
|
<div class="h2 mb-0 text-truncate">
|
||||||
|
{{ .Server.DaemonType }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 col-lg-3">
|
<div class="col-sm-6 col-lg-3">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="subheader">Version</div>
|
<div class="subheader">Versão</div>
|
||||||
<div class="h2 mb-0 text-truncate">{{ .Server.Version }}</div>
|
<div class="h2 mb-0 text-truncate">{{ .Server.Version }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,7 +38,7 @@
|
|||||||
<div class="col-sm-6 col-lg-3">
|
<div class="col-sm-6 col-lg-3">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="subheader">Zones</div>
|
<div class="subheader">Zonas</div>
|
||||||
<div class="h2 mb-0">{{ len .Zones }}</div>
|
<div class="h2 mb-0">{{ len .Zones }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,25 +47,30 @@
|
|||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Recent zones</h3>
|
<h3 class="card-title">Zonas recentes</h3>
|
||||||
</div>
|
</div>
|
||||||
{{ if .Zones }}
|
{{ if .Zones }}
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
{{ range .Zones }}
|
{{ range .Zones }}
|
||||||
<a class="list-group-item list-group-item-action" href="/zones/{{ .ID }}">
|
<a
|
||||||
|
class="list-group-item list-group-item-action"
|
||||||
|
href="/zones/{{ .ID }}"
|
||||||
|
>
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col text-truncate">
|
<div class="col text-truncate">
|
||||||
<strong>{{ .Name }}</strong>
|
<strong>{{ .Name }}</strong>
|
||||||
<div class="text-secondary text-truncate">{{ .DisplayKind }} · serial {{ .Serial }}</div>
|
<div class="text-secondary text-truncate">
|
||||||
|
{{ .DisplayKind }} · serial {{ .Serial }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<div class="card-body text-secondary">No zones returned by PowerDNS.</div>
|
<div class="card-body text-secondary">
|
||||||
|
Nenhuma zona retornada pelo PowerDNS.
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }} {{ template "layout" . }}
|
||||||
|
|
||||||
{{ template "layout" . }}
|
|
||||||
|
|||||||
@@ -3,23 +3,35 @@
|
|||||||
<div class="col-md-6 col-lg-4">
|
<div class="col-md-6 col-lg-4">
|
||||||
<div class="card card-md">
|
<div class="card card-md">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="h2 text-center mb-2">Login</h2>
|
<h2 class="h2 text-center mb-2">Entrar</h2>
|
||||||
<form method="post" action="/login">
|
<form method="post" action="/login">
|
||||||
<input type="hidden" name="next" value="{{ .Next }}">
|
<input type="hidden" name="next" value="{{ .Next }}" />
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Username</label>
|
<label class="form-label">Usuário</label>
|
||||||
<input class="form-control" name="username" autocomplete="username" required autofocus>
|
<input
|
||||||
|
class="form-control"
|
||||||
|
name="username"
|
||||||
|
autocomplete="username"
|
||||||
|
required
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Password</label>
|
<label class="form-label">Senha</label>
|
||||||
<input class="form-control" name="password" type="password" autocomplete="current-password" required>
|
<input
|
||||||
|
class="form-control"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
autocomplete="current-password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary w-100" type="submit">Sign in</button>
|
<button class="btn btn-primary w-100" type="submit">
|
||||||
|
Entrar
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }} {{ template "layout" . }}
|
||||||
|
|
||||||
{{ template "layout" . }}
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
<h2 class="page-title">{{ .RecordForm.Title }}</h2>
|
<h2 class="page-title">{{ .RecordForm.Title }}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto ms-auto">
|
<div class="col-auto ms-auto">
|
||||||
<a class="btn btn-outline-secondary" href="/zones/{{ .ZoneID }}">Back to records</a>
|
<a class="btn btn-outline-secondary" href="/zones/{{ .ZoneID }}"
|
||||||
|
>Voltar para registros</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,47 +20,93 @@
|
|||||||
{{ if .RecordForm.IsEdit }}
|
{{ if .RecordForm.IsEdit }}
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<div class="subheader">Name</div>
|
<div class="subheader">Nome</div>
|
||||||
<div class="h3 mb-0">{{ .RecordForm.Name }}</div>
|
<div class="h3 mb-0">{{ .RecordForm.Name }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="subheader">Type</div>
|
<div class="subheader">Tipo</div>
|
||||||
<div class="h3 mb-0">{{ .RecordForm.Type }}</div>
|
<div class="h3 mb-0">{{ .RecordForm.Type }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
<form method="post" action="{{ if .RecordForm.IsEdit }}/zones/{{ .ZoneID }}/rrsets/edit?name={{ urlQuery .RecordForm.Name }}&type={{ urlQuery .RecordForm.Type }}{{ else }}/zones/{{ .ZoneID }}/rrsets{{ end }}">
|
<form
|
||||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
method="post"
|
||||||
|
action="{{ if .RecordForm.IsEdit }}/zones/{{ .ZoneID }}/rrsets/edit?name={{ urlQuery .RecordForm.Name }}&type={{ urlQuery .RecordForm.Type }}{{ else }}/zones/{{ .ZoneID }}/rrsets{{ end }}"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="csrf_token"
|
||||||
|
value="{{ .CSRFToken }}"
|
||||||
|
/>
|
||||||
{{ if not .RecordForm.IsEdit }}
|
{{ if not .RecordForm.IsEdit }}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Name</label>
|
<label class="form-label">Nome</label>
|
||||||
<input class="form-control" name="name" value="{{ .RecordForm.Name }}" placeholder="www.{{ .Zone.Name }}" required>
|
<input
|
||||||
|
class="form-control"
|
||||||
|
name="name"
|
||||||
|
value="{{ .RecordForm.Name }}"
|
||||||
|
placeholder="www.{{ .Zone.Name }}"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Type</label>
|
<label class="form-label">Tipo</label>
|
||||||
<select class="form-select" name="type" required>
|
<select class="form-select" name="type" required>
|
||||||
{{ range .RecordTypes }}
|
{{ range .RecordTypes }}
|
||||||
<option {{ if eq . $.RecordForm.Type }}selected{{ end }}>{{ . }}</option>
|
<option
|
||||||
|
{{
|
||||||
|
if
|
||||||
|
eq
|
||||||
|
.
|
||||||
|
$.RecordForm.Type
|
||||||
|
}}selected{{
|
||||||
|
end
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{{ . }}
|
||||||
|
</option>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">TTL</label>
|
<label class="form-label">TTL</label>
|
||||||
<input class="form-control" name="ttl" type="number" min="1" value="{{ .RecordForm.TTL }}" required>
|
<input
|
||||||
|
class="form-control"
|
||||||
|
name="ttl"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
value="{{ .RecordForm.TTL }}"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Records</label>
|
<label class="form-label">Registros</label>
|
||||||
<textarea class="form-control" name="records" rows="8" placeholder="One record value per line" required>{{ .RecordForm.Records }}</textarea>
|
<textarea
|
||||||
<div class="form-hint">Strict formats are enforced. TXT and CAA string values must be quoted. Adding A, AAAA, CAA, MX, NS, SRV, or TXT records appends to an existing RRset with the same name and type. SOA records can be edited here but cannot be deleted.</div>
|
class="form-control"
|
||||||
|
name="records"
|
||||||
|
rows="8"
|
||||||
|
placeholder="Um valor de registro por linha"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
{{ .RecordForm.Records }}</textarea
|
||||||
|
>
|
||||||
|
<div class="form-hint">
|
||||||
|
Formato estrito exigido: Valores de texto TXT e CAA
|
||||||
|
devem estar entre aspas. Adicionar registros A,
|
||||||
|
AAAA, CAA, MX, NS, SRV ou TXT acrescenta ao conjunto
|
||||||
|
de registros existente com o mesmo nome e tipo.
|
||||||
|
Registros SOA podem ser editados aqui, mas não podem
|
||||||
|
ser excluidos.
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary" type="submit">{{ .RecordForm.SubmitLabel }}</button>
|
</div>
|
||||||
|
<button class="btn btn-primary" type="submit">
|
||||||
|
{{ .RecordForm.SubmitLabel }}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }} {{ template "layout" . }}
|
||||||
|
|
||||||
{{ template "layout" . }}
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
<div class="page-header d-print-none">
|
<div class="page-header d-print-none">
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="page-pretitle">Zone</div>
|
<div class="page-pretitle">Zona</div>
|
||||||
<h2 class="page-title">{{ .Zone.Name }}</h2>
|
<h2 class="page-title">{{ .Zone.Name }}</h2>
|
||||||
<div class="text-secondary">{{ len .Zone.RRSets }} RRsets</div>
|
<div class="text-secondary">{{ len .Zone.RRSets }} conjuntos de registros</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto ms-auto">
|
<div class="col-auto ms-auto">
|
||||||
<div class="btn-list">
|
<div class="btn-list">
|
||||||
<a class="btn btn-outline-secondary" href="/zones">Back to zones</a>
|
<a class="btn btn-outline-secondary" href="/zones">Voltar para zonas</a>
|
||||||
<a class="btn btn-primary" href="/zones/{{ .ZoneID }}/rrsets/new">Add record</a>
|
<a class="btn btn-primary" href="/zones/{{ .ZoneID }}/rrsets/new">Adicionar registro</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -17,17 +17,17 @@
|
|||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Records</h3>
|
<h3 class="card-title">Registros</h3>
|
||||||
</div>
|
</div>
|
||||||
{{ if .Zone.RRSets }}
|
{{ if .Zone.RRSets }}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-vcenter card-table">
|
<table class="table table-vcenter card-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Nome</th>
|
||||||
<th>Type</th>
|
<th>Tipo</th>
|
||||||
<th>TTL</th>
|
<th>TTL</th>
|
||||||
<th>Records</th>
|
<th>Registros</th>
|
||||||
<th class="w-1"></th>
|
<th class="w-1"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -46,13 +46,13 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-list flex-nowrap">
|
<div class="btn-list flex-nowrap">
|
||||||
<a class="btn btn-outline-primary btn-sm" href="/zones/{{ $.ZoneID }}/rrsets/edit?name={{ urlQuery .Name }}&type={{ urlQuery .Type }}">Edit</a>
|
<a class="btn btn-outline-primary btn-sm" href="/zones/{{ $.ZoneID }}/rrsets/edit?name={{ urlQuery .Name }}&type={{ urlQuery .Type }}">Editar</a>
|
||||||
{{ if not (isSOA .Type) }}
|
{{ if not (isSOA .Type) }}
|
||||||
<form method="post" action="/zones/{{ $.ZoneID }}/rrsets/delete">
|
<form method="post" action="/zones/{{ $.ZoneID }}/rrsets/delete">
|
||||||
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
||||||
<input type="hidden" name="name" value="{{ .Name }}">
|
<input type="hidden" name="name" value="{{ .Name }}">
|
||||||
<input type="hidden" name="type" value="{{ .Type }}">
|
<input type="hidden" name="type" value="{{ .Type }}">
|
||||||
<button class="btn btn-outline-danger btn-sm" type="submit">Delete</button>
|
<button class="btn btn-outline-danger btn-sm" type="submit">Excluir</button>
|
||||||
</form>
|
</form>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<div class="card-body text-secondary">No records returned for this zone.</div>
|
<div class="card-body text-secondary">Nenhum registro retornado para esta zona.</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
<div class="page-header d-print-none">
|
<div class="page-header d-print-none">
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="page-pretitle">Authoritative DNS</div>
|
<div class="page-pretitle">DNS autoritativo</div>
|
||||||
<h2 class="page-title">Zones</h2>
|
<h2 class="page-title">Zonas</h2>
|
||||||
<div class="text-secondary">{{ len .Zones }} zones loaded from PowerDNS.</div>
|
<div class="text-secondary">{{ len .Zones }} zonas carregadas do PowerDNS.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -13,15 +13,15 @@
|
|||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Existing zones</h3>
|
<h3 class="card-title">Zonas existentes</h3>
|
||||||
</div>
|
</div>
|
||||||
{{ if .Zones }}
|
{{ if .Zones }}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-vcenter card-table">
|
<table class="table table-vcenter card-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Nome</th>
|
||||||
<th>Kind</th>
|
<th>Tipo</th>
|
||||||
<th>Serial</th>
|
<th>Serial</th>
|
||||||
<th class="w-1"></th>
|
<th class="w-1"></th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<form method="post" action="/zones/{{ .ID }}/delete">
|
<form method="post" action="/zones/{{ .ID }}/delete">
|
||||||
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
||||||
<button class="btn btn-outline-danger btn-sm" type="submit">Delete</button>
|
<button class="btn btn-outline-danger btn-sm" type="submit">Excluir</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<div class="card-body text-secondary">No zones returned by PowerDNS.</div>
|
<div class="card-body text-secondary">Nenhuma zona retornada pelo PowerDNS.</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,17 +52,17 @@
|
|||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Add zone</h3>
|
<h3 class="card-title">Adicionar zona</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="post" action="/zones">
|
<form method="post" action="/zones">
|
||||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Zone name</label>
|
<label class="form-label">Nome da zona</label>
|
||||||
<input class="form-control" name="name" placeholder="example.org." required>
|
<input class="form-control" name="name" placeholder="example.org." required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Kind</label>
|
<label class="form-label">Tipo</label>
|
||||||
<select class="form-select" name="kind" required>
|
<select class="form-select" name="kind" required>
|
||||||
<option>Native</option>
|
<option>Native</option>
|
||||||
<option>Master</option>
|
<option>Master</option>
|
||||||
@@ -70,14 +70,14 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Nameservers</label>
|
<label class="form-label">Servidores de nomes</label>
|
||||||
<textarea class="form-control" name="nameservers" rows="4" placeholder="ns1.example.org. ns2.example.org."></textarea>
|
<textarea class="form-control" name="nameservers" rows="4" placeholder="ns1.example.org. ns2.example.org."></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Masters</label>
|
<label class="form-label">Servidores mestres</label>
|
||||||
<textarea class="form-control" name="masters" rows="3" placeholder="Required for Slave zones"></textarea>
|
<textarea class="form-control" name="masters" rows="3" placeholder="Obrigatorio para zonas Slave"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary" type="submit">Create zone</button>
|
<button class="btn btn-primary" type="submit">Criar zona</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user