diff --git a/.nixd.json b/.nixd.json new file mode 100644 index 0000000..9e729e8 --- /dev/null +++ b/.nixd.json @@ -0,0 +1,13 @@ +{ + "formatting": { + "command": "nixpkgs-fmt" + }, + "eval": { + "target": { + "args": [ + "--expr", + "with import { };" + ] + } + } +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..baa63bc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "sqltools.connections": [ + { + "mysqlOptions": { + "authProtocol": "default", + "enableSsl": "Disabled" + }, + "previewLimit": 50, + "server": "mysql.dev", + "port": 3306, + "driver": "MySQL", + "name": "recored-ui", + "database": "recoredui", + "username": "recoredui" + } + ] +} \ No newline at end of file diff --git a/TODO b/TODO index ff0ff00..d3947a7 100644 --- a/TODO +++ b/TODO @@ -2,6 +2,7 @@ - [x] Web UI - [x] i18n - [x] modals +- [] debug - [] swagger - [] Nix Module - [] RBAC diff --git a/cmd/server/server.go b/cmd/server/server.go index ba64826..d2478a3 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -18,12 +18,12 @@ var Command = &cli.Command{ }), altsrc.NewStringFlag(&cli.StringFlag{ Name: "listen", - Value: "[::]", + Value: "::", Usage: "IP for listen at", }), altsrc.NewIntFlag(&cli.IntFlag{ Name: "port", - Value: 8080, + Value: 3000, Usage: "Port for listen at", }), }, diff --git a/controllers/domain.go b/controllers/domain.go index b45ed2f..f16064c 100644 --- a/controllers/domain.go +++ b/controllers/domain.go @@ -5,6 +5,8 @@ import ( "reCoreD-UI/database" "reCoreD-UI/models" "strconv" + + "github.com/sirupsen/logrus" ) type domainsDAO struct { @@ -28,6 +30,7 @@ func CreateDomain(d *models.Domain) (*models.Domain, error) { r.Name = "@" r.RecordType = models.RecordTypeSOA r.Content = d.GenerateSOA() + logrus.Debug(r) if err := r.CheckZone(); err != nil { tx.Rollback() return nil, err @@ -41,7 +44,7 @@ func CreateDomain(d *models.Domain) (*models.Domain, error) { for i, ns := range nss { record := &models.Record[models.NSRecord]{ Zone: d.WithDotEnd(), - RecordType: models.RecordTypeSOA, + RecordType: models.RecordTypeNS, Name: fmt.Sprintf("ns%d", i+1), } record.Content.Host = ns @@ -123,13 +126,13 @@ func DeleteDomain(id string) error { } tx := database.Client.Begin() - domain, err := (domainsDAO{}).GetOne(tx, &models.Domain{ID: ID}) + domain, err := (domainsDAO{}).GetOne(tx, &models.Domain{ID: uint(ID)}) if err != nil { tx.Rollback() return err } - if err := (domainsDAO{}).Delete(tx, &models.Domain{ID: ID}); err != nil { + if err := (domainsDAO{}).Delete(tx, &models.Domain{ID: uint(ID)}); err != nil { tx.Rollback() return err } diff --git a/controllers/metrics.go b/controllers/metrics.go index 198e4ed..b57efec 100644 --- a/controllers/metrics.go +++ b/controllers/metrics.go @@ -37,7 +37,7 @@ func RegisterMetrics() { GinMetrics := ginprometheus.NewPrometheus("recoredui") for _, v := range GinMetrics.MetricsList { - prometheus.MustRegister(v.MetricCollector) + prometheus.Register(v.MetricCollector) } } diff --git a/controllers/record.go b/controllers/record.go index 07b3725..41896ad 100644 --- a/controllers/record.go +++ b/controllers/record.go @@ -42,7 +42,7 @@ func CreateRecords(rs []models.IRecord) error { return err } } - + return tx.Commit().Error } @@ -68,7 +68,7 @@ func DeleteRecord(domain, id string) error { } tx := database.Client.Begin() - record, err := (recordsDAO{}).GetOne(tx, &models.Record[models.RecordContentDefault]{ID: ID, Zone: fmt.Sprintf("%s.", domain)}) + record, err := (recordsDAO{}).GetOne(tx, &models.Record[models.RecordContentDefault]{ID: uint(ID), Zone: fmt.Sprintf("%s.", domain)}) if err != nil { tx.Rollback() return err diff --git a/database/basedao.go b/database/basedao.go index f711915..3f87251 100644 --- a/database/basedao.go +++ b/database/basedao.go @@ -3,6 +3,7 @@ package database import ( "errors" + "github.com/huandu/go-clone" "gorm.io/gorm" ) @@ -14,41 +15,40 @@ func (b BaseDAO[T]) Migrate(db *gorm.DB, e T) error { func (BaseDAO[T]) GetAll(db *gorm.DB, e T, cond ...T) ([]T, error) { var r []T - tx := db + tx := db.Model(e) for _, c := range cond { tx = tx.Where(c) } - if err := tx.Find(&r, e).Error; err != nil { + rows, err := tx.Rows() + if err != nil { return nil, err } + defer rows.Close() + + for rows.Next() { + if err := db.ScanRows(rows, e); err != nil { + return nil, err + } + + i := clone.Clone(e).(T) + + r = append(r, i) + } + return r, nil } func (BaseDAO[T]) GetOne(db *gorm.DB, e T, cond ...T) (T, error) { - var r T tx := db for _, c := range cond { tx = tx.Where(c) } - if err := tx.First(&r, e).Error; err != nil { - return r, err + if err := tx.First(e).Error; err != nil { + return e, err } - return r, nil -} - -func (BaseDAO[T]) GetSome(db *gorm.DB, e T, limit, offset int, cond ...T) ([]T, error) { - var r []T - tx := db - for _, c := range cond { - tx = tx.Where(c) - } - - if err := tx.Find(&r, e).Limit(limit).Offset(offset).Error; err != nil { - return nil, err - } - return r, nil + return e, nil } func (BaseDAO[T]) Create(db *gorm.DB, e T) (T, error) { diff --git a/flake.lock b/flake.lock index 1cdfbb3..5a33f55 100644 --- a/flake.lock +++ b/flake.lock @@ -1,39 +1,6 @@ { "nodes": { - "naersk": { - "inputs": { - "nixpkgs": "nixpkgs" - }, - "locked": { - "lastModified": 1698420672, - "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", - "owner": "nix-community", - "repo": "naersk", - "rev": "aeb58d5e8faead8980a807c840232697982d47b9", - "type": "github" - }, - "original": { - "owner": "nix-community", - "ref": "master", - "repo": "naersk", - "type": "github" - } - }, "nixpkgs": { - "locked": { - "lastModified": 1711715736, - "narHash": "sha256-9slQ609YqT9bT/MNX9+5k5jltL9zgpn36DpFB7TkttM=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "807c549feabce7eddbf259dbdcec9e0600a0660d", - "type": "github" - }, - "original": { - "id": "nixpkgs", - "type": "indirect" - } - }, - "nixpkgs_2": { "locked": { "lastModified": 1711715736, "narHash": "sha256-9slQ609YqT9bT/MNX9+5k5jltL9zgpn36DpFB7TkttM=", @@ -51,8 +18,7 @@ }, "root": { "inputs": { - "naersk": "naersk", - "nixpkgs": "nixpkgs_2", + "nixpkgs": "nixpkgs", "utils": "utils" } }, diff --git a/flake.nix b/flake.nix index e1806cf..b5d86d1 100644 --- a/flake.nix +++ b/flake.nix @@ -20,29 +20,23 @@ }; inputs = { - naersk.url = "github:nix-community/naersk/master"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs, utils, naersk }: + outputs = { self, nixpkgs, utils }: utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; }; - naersk-lib = pkgs.callPackage naersk { }; in { - defaultPackage = naersk-lib.buildPackage { - src = ./.; - buildInputs = with pkgs; [ - - ]; - }; + defaultPackage = {}; devShell = with pkgs; mkShell { buildInputs = [ go nodejs + dig tokei ]; GOPATH = "/home/coder/.cache/go"; diff --git a/go.mod b/go.mod index b714989..96f855e 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect + github.com/huandu/go-clone v1.7.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect diff --git a/go.sum b/go.sum index 9a50c38..4a925fa 100644 --- a/go.sum +++ b/go.sum @@ -354,6 +354,9 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/go-clone v1.7.2 h1:3+Aq0Ed8XK+zKkLjE2dfHg0XrpIfcohBE1K+c8Usxoo= +github.com/huandu/go-clone v1.7.2/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= diff --git a/models/domain.go b/models/domain.go index 01e6104..a3ad043 100644 --- a/models/domain.go +++ b/models/domain.go @@ -6,17 +6,17 @@ import ( ) type Domain struct { - ID int `gorm:"primaryKey" json:"id"` + ID uint `gorm:"primaryKey" json:"id"` DomainName string `gorm:"unique,not null,size:255" json:"domain_name"` //SOA Info MainDNS string `gorm:"not null;size:255" json:"main_dns"` AdminEmail string `gorm:"not null;size:255" json:"admin_email"` SerialNumber int64 `gorm:"not null;default:1" json:"serial_number"` - RefreshInterval uint32 `gorm:"not null;size:255,default:\"86400\"" json:"refresh_interval"` - RetryInterval uint32 `gorm:"not null;size:255,default:\"7200\"" json:"retry_interval"` - ExpiryPeriod uint32 `gorm:"not null;size:255,default:\"3600000\"" json:"expiry_period"` - NegativeTtl uint32 `gorm:"not null;size:255,default:\"86400\"" json:"negative_ttl"` + RefreshInterval uint32 `gorm:"type:uint;not null;default:86400" json:"refresh_interval"` + RetryInterval uint32 `gorm:"type:uint;not null;default:7200" json:"retry_interval"` + ExpiryPeriod uint32 `gorm:"type:uint;not null;default:3600000" json:"expiry_period"` + NegativeTtl uint32 `gorm:"type:uint;not null;default:86400" json:"negative_ttl"` } func (d *Domain) EmailSOAForamt() string { @@ -53,8 +53,13 @@ func (d *Domain) GenerateSOA() SOARecord { return r } +func (d *Domain) GetValue() Domain { + return *d +} + type IDomain interface { EmailSOAForamt() string WithDotEnd() string GenerateSOA() SOARecord + GetValue() Domain } diff --git a/models/record.go b/models/record.go index f677a5e..d34c04a 100644 --- a/models/record.go +++ b/models/record.go @@ -25,7 +25,7 @@ type recordContentTypes interface { } type Record[T recordContentTypes] struct { - ID int `gorm:"primaryKey" json:"id"` + ID uint `gorm:"primaryKey" json:"id"` Zone string `gorm:"not null;size:255" json:"zone"` Name string `gorm:"not null;size:255" json:"name"` Ttl int `json:"ttl"` @@ -34,11 +34,11 @@ type Record[T recordContentTypes] struct { } func (*Record[T]) TableName() string { - return "coredns_record" + return "coredns_records" } func (r *Record[T]) CheckZone() error { - if strings.HasSuffix(r.Zone, ".") { + if !strings.HasSuffix(r.Zone, ".") { return ErrorZoneNotEndWithDot } return nil @@ -65,6 +65,10 @@ func (r *Record[T]) GetType() string { return r.RecordType } +func (r *Record[T]) GetValue() IRecord { + return r.ToEntity() +} + type IRecord interface { TableName() string CheckZone() error @@ -72,4 +76,5 @@ type IRecord interface { ToEntity() IRecord FromEntity(any) error GetType() string + GetValue() IRecord } diff --git a/models/settings.go b/models/settings.go index f55285a..20b75ec 100644 --- a/models/settings.go +++ b/models/settings.go @@ -2,8 +2,6 @@ package models import ( "fmt" - - "gorm.io/gorm" ) const ( @@ -13,7 +11,7 @@ const ( ) type Settings struct { - gorm.Model + ID uint `gorm:"primaryKey"` Key string `gorm:"unique;not null;size:255"` Value string `gorm:"not null;size:255"` } @@ -22,6 +20,11 @@ func (s *Settings) String() string { return fmt.Sprintf("%s: %s", s.Key, s.Value) } +func (s *Settings) GetValue() Settings { + return *s +} + type ISettings interface { String() string + GetValue() Settings } diff --git a/server/route.go b/server/route.go index 543badc..3e69e4e 100644 --- a/server/route.go +++ b/server/route.go @@ -4,7 +4,6 @@ import ( "net/http" "path" "reCoreD-UI/controllers" - "strings" "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -24,11 +23,21 @@ func (s *Server) setupRoute() { if err != nil { logrus.Fatal(err) } + logrus.Debugf("got %s:%s", username, password) - swaggerHandler := gin.New() - swaggerHandler.GET(path.Join(swaggerPrefix, "*any"), ginSwagger.WrapHandler(swaggerfiles.Handler)) + server := s.webServer.Group(s.prefix) - metricHandler := gin.New() + swaggerHandler := server + if s.debug { + swaggerHandler.GET(path.Join(swaggerPrefix, "*any"), ginSwagger.WrapHandler(swaggerfiles.Handler)) + } else { + swaggerHandler.GET(path.Join(swaggerPrefix, "*any"), func(ctx *gin.Context) { + ctx.HTML(http.StatusNotFound, "", nil) + }) + } + + controllers.RegisterMetrics() + metricHandler := server metricHandler.GET(metricPrefix, func(ctx *gin.Context) { if err := controllers.RefreshMetrics(); err != nil { logrus.Error(err) @@ -36,11 +45,18 @@ func (s *Server) setupRoute() { promhttp.Handler().ServeHTTP(ctx.Writer, ctx.Request) }) - apiHandler := gin.New() + apiHandler := server groupV1 := apiHandler.Group(apiPrefix, gin.BasicAuth(gin.Accounts{ username: password, - })).Group("/v1") + }), func(ctx *gin.Context) { + _, ok := ctx.Get(gin.AuthUserKey) + if !ok { + ctx.AbortWithStatusJSON(http.StatusUnauthorized, Response{ + Succeed: false, + }) + } + }).Group("/v1") domains := groupV1.Group("/domains") domains. @@ -57,15 +73,15 @@ func (s *Server) setupRoute() { PUT("/:domain", updateRecord). DELETE("/:domain/:id", deleteRecord) - server := s.webServer.Group(s.prefix) - server.Use(func(ctx *gin.Context) { + /*server := s.webServer.Group(s.prefix) + server.Use(apiHandler.HandleContext, metricHandler.HandleContext, func(ctx *gin.Context) { uri := ctx.Request.RequestURI logrus.Debug(uri) switch { case strings.HasPrefix(uri, path.Join(s.prefix, apiPrefix)): - apiHandler.HandleContext(ctx) + //apiHandler.HandleContext(ctx) case strings.HasPrefix(uri, path.Join(s.prefix, metricPrefix)): - metricHandler.HandleContext(ctx) + //metricHandler.HandleContext(ctx) case strings.HasPrefix(uri, path.Join(s.prefix, swaggerPrefix)): if s.debug { swaggerHandler.HandleContext(ctx) @@ -75,5 +91,8 @@ func (s *Server) setupRoute() { default: staticFileHandler()(ctx) } - }) + })*/ + + server.GET("/", staticFileHandler()) + server.GET("/assets/*any", staticFileHandler()) } diff --git a/server/server.go b/server/server.go index 4cca6ab..5f9a6f5 100644 --- a/server/server.go +++ b/server/server.go @@ -22,6 +22,9 @@ func NewServer(c *cli.Context) (*Server, error) { } if c.Bool("debug") { database.Client = database.Client.Debug() + gin.SetMode(gin.DebugMode) + } else { + gin.SetMode(gin.ReleaseMode) } return &Server{ diff --git a/web/src/locale/en.ts b/web/src/locale/en.ts index 83a11d8..8138d1e 100644 --- a/web/src/locale/en.ts +++ b/web/src/locale/en.ts @@ -91,7 +91,8 @@ export default { dotAndMinus: 'should not start or end with "." "-"', doubleDots: 'should have no contianus "."', logerThan63: 'should not longer than 63 characters splited by "."' - } + }, + tooLong: 'too long' } } } \ No newline at end of file diff --git a/web/src/locale/zh.ts b/web/src/locale/zh.ts index 5a5c460..017653a 100644 --- a/web/src/locale/zh.ts +++ b/web/src/locale/zh.ts @@ -91,7 +91,8 @@ export default { dotAndMinus: '资源记录不能以 "."、"-" 开头或结尾', doubleDots: '资源记录不能有连续的 "."', logerThan63: '资源记录以 "." 分割的每个字符串长度不能超过63字符' - } + }, + tooLong: '记录值过长' } } } \ No newline at end of file diff --git a/web/src/stores/records.ts b/web/src/stores/records.ts index 96fe160..8fe74f6 100644 --- a/web/src/stores/records.ts +++ b/web/src/stores/records.ts @@ -35,6 +35,7 @@ export class TXTRecord { static validate(v: TXTRecord): true | Error { if (!v.text || v.text === '') return new Error(t('common.mandatory')) + if (v.text.length > 512) return new Error('records.errors.tooLong') return true }