test stage 1

This commit is contained in:
Sense T 2024-04-19 09:44:37 +08:00
parent b583720223
commit 88b2255f8b
20 changed files with 131 additions and 95 deletions

13
.nixd.json Normal file
View File

@ -0,0 +1,13 @@
{
"formatting": {
"command": "nixpkgs-fmt"
},
"eval": {
"target": {
"args": [
"--expr",
"with import <nixpkgs> { };"
]
}
}
}

17
.vscode/settings.json vendored Normal file
View File

@ -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"
}
]
}

1
TODO
View File

@ -2,6 +2,7 @@
- [x] Web UI - [x] Web UI
- [x] i18n - [x] i18n
- [x] modals - [x] modals
- [] debug
- [] swagger - [] swagger
- [] Nix Module - [] Nix Module
- [] RBAC - [] RBAC

View File

@ -18,12 +18,12 @@ var Command = &cli.Command{
}), }),
altsrc.NewStringFlag(&cli.StringFlag{ altsrc.NewStringFlag(&cli.StringFlag{
Name: "listen", Name: "listen",
Value: "[::]", Value: "::",
Usage: "IP for listen at", Usage: "IP for listen at",
}), }),
altsrc.NewIntFlag(&cli.IntFlag{ altsrc.NewIntFlag(&cli.IntFlag{
Name: "port", Name: "port",
Value: 8080, Value: 3000,
Usage: "Port for listen at", Usage: "Port for listen at",
}), }),
}, },

View File

@ -5,6 +5,8 @@ import (
"reCoreD-UI/database" "reCoreD-UI/database"
"reCoreD-UI/models" "reCoreD-UI/models"
"strconv" "strconv"
"github.com/sirupsen/logrus"
) )
type domainsDAO struct { type domainsDAO struct {
@ -28,6 +30,7 @@ func CreateDomain(d *models.Domain) (*models.Domain, error) {
r.Name = "@" r.Name = "@"
r.RecordType = models.RecordTypeSOA r.RecordType = models.RecordTypeSOA
r.Content = d.GenerateSOA() r.Content = d.GenerateSOA()
logrus.Debug(r)
if err := r.CheckZone(); err != nil { if err := r.CheckZone(); err != nil {
tx.Rollback() tx.Rollback()
return nil, err return nil, err
@ -41,7 +44,7 @@ func CreateDomain(d *models.Domain) (*models.Domain, error) {
for i, ns := range nss { for i, ns := range nss {
record := &models.Record[models.NSRecord]{ record := &models.Record[models.NSRecord]{
Zone: d.WithDotEnd(), Zone: d.WithDotEnd(),
RecordType: models.RecordTypeSOA, RecordType: models.RecordTypeNS,
Name: fmt.Sprintf("ns%d", i+1), Name: fmt.Sprintf("ns%d", i+1),
} }
record.Content.Host = ns record.Content.Host = ns
@ -123,13 +126,13 @@ func DeleteDomain(id string) error {
} }
tx := database.Client.Begin() 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 { if err != nil {
tx.Rollback() tx.Rollback()
return err 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() tx.Rollback()
return err return err
} }

View File

@ -37,7 +37,7 @@ func RegisterMetrics() {
GinMetrics := ginprometheus.NewPrometheus("recoredui") GinMetrics := ginprometheus.NewPrometheus("recoredui")
for _, v := range GinMetrics.MetricsList { for _, v := range GinMetrics.MetricsList {
prometheus.MustRegister(v.MetricCollector) prometheus.Register(v.MetricCollector)
} }
} }

View File

@ -68,7 +68,7 @@ func DeleteRecord(domain, id string) error {
} }
tx := database.Client.Begin() 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 { if err != nil {
tx.Rollback() tx.Rollback()
return err return err

View File

@ -3,6 +3,7 @@ package database
import ( import (
"errors" "errors"
"github.com/huandu/go-clone"
"gorm.io/gorm" "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) { func (BaseDAO[T]) GetAll(db *gorm.DB, e T, cond ...T) ([]T, error) {
var r []T var r []T
tx := db tx := db.Model(e)
for _, c := range cond { for _, c := range cond {
tx = tx.Where(c) tx = tx.Where(c)
} }
if err := tx.Find(&r, e).Error; err != nil { rows, err := tx.Rows()
if err != nil {
return nil, err 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 return r, nil
} }
func (BaseDAO[T]) GetOne(db *gorm.DB, e T, cond ...T) (T, error) { func (BaseDAO[T]) GetOne(db *gorm.DB, e T, cond ...T) (T, error) {
var r T
tx := db tx := db
for _, c := range cond { for _, c := range cond {
tx = tx.Where(c) tx = tx.Where(c)
} }
if err := tx.First(&r, e).Error; err != nil { if err := tx.First(e).Error; err != nil {
return r, err return e, err
} }
return r, nil return e, 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
} }
func (BaseDAO[T]) Create(db *gorm.DB, e T) (T, error) { func (BaseDAO[T]) Create(db *gorm.DB, e T) (T, error) {

View File

@ -1,39 +1,6 @@
{ {
"nodes": { "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": { "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": { "locked": {
"lastModified": 1711715736, "lastModified": 1711715736,
"narHash": "sha256-9slQ609YqT9bT/MNX9+5k5jltL9zgpn36DpFB7TkttM=", "narHash": "sha256-9slQ609YqT9bT/MNX9+5k5jltL9zgpn36DpFB7TkttM=",
@ -51,8 +18,7 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"naersk": "naersk", "nixpkgs": "nixpkgs",
"nixpkgs": "nixpkgs_2",
"utils": "utils" "utils": "utils"
} }
}, },

View File

@ -20,29 +20,23 @@
}; };
inputs = { inputs = {
naersk.url = "github:nix-community/naersk/master";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
utils.url = "github:numtide/flake-utils"; utils.url = "github:numtide/flake-utils";
}; };
outputs = { self, nixpkgs, utils, naersk }: outputs = { self, nixpkgs, utils }:
utils.lib.eachDefaultSystem (system: utils.lib.eachDefaultSystem (system:
let let
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
naersk-lib = pkgs.callPackage naersk { };
in in
{ {
defaultPackage = naersk-lib.buildPackage { defaultPackage = {};
src = ./.;
buildInputs = with pkgs; [
];
};
devShell = with pkgs; mkShell { devShell = with pkgs; mkShell {
buildInputs = [ buildInputs = [
go go
nodejs nodejs
dig
tokei tokei
]; ];
GOPATH = "/home/coder/.cache/go"; GOPATH = "/home/coder/.cache/go";

1
go.mod
View File

@ -35,6 +35,7 @@ require (
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // 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/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect

3
go.sum
View File

@ -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/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 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/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/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-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=

View File

@ -6,17 +6,17 @@ import (
) )
type Domain struct { 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"` DomainName string `gorm:"unique,not null,size:255" json:"domain_name"`
//SOA Info //SOA Info
MainDNS string `gorm:"not null;size:255" json:"main_dns"` MainDNS string `gorm:"not null;size:255" json:"main_dns"`
AdminEmail string `gorm:"not null;size:255" json:"admin_email"` AdminEmail string `gorm:"not null;size:255" json:"admin_email"`
SerialNumber int64 `gorm:"not null;default:1" json:"serial_number"` SerialNumber int64 `gorm:"not null;default:1" json:"serial_number"`
RefreshInterval uint32 `gorm:"not null;size:255,default:\"86400\"" json:"refresh_interval"` RefreshInterval uint32 `gorm:"type:uint;not null;default:86400" json:"refresh_interval"`
RetryInterval uint32 `gorm:"not null;size:255,default:\"7200\"" json:"retry_interval"` RetryInterval uint32 `gorm:"type:uint;not null;default:7200" json:"retry_interval"`
ExpiryPeriod uint32 `gorm:"not null;size:255,default:\"3600000\"" json:"expiry_period"` ExpiryPeriod uint32 `gorm:"type:uint;not null;default:3600000" json:"expiry_period"`
NegativeTtl uint32 `gorm:"not null;size:255,default:\"86400\"" json:"negative_ttl"` NegativeTtl uint32 `gorm:"type:uint;not null;default:86400" json:"negative_ttl"`
} }
func (d *Domain) EmailSOAForamt() string { func (d *Domain) EmailSOAForamt() string {
@ -53,8 +53,13 @@ func (d *Domain) GenerateSOA() SOARecord {
return r return r
} }
func (d *Domain) GetValue() Domain {
return *d
}
type IDomain interface { type IDomain interface {
EmailSOAForamt() string EmailSOAForamt() string
WithDotEnd() string WithDotEnd() string
GenerateSOA() SOARecord GenerateSOA() SOARecord
GetValue() Domain
} }

View File

@ -25,7 +25,7 @@ type recordContentTypes interface {
} }
type Record[T recordContentTypes] struct { 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"` Zone string `gorm:"not null;size:255" json:"zone"`
Name string `gorm:"not null;size:255" json:"name"` Name string `gorm:"not null;size:255" json:"name"`
Ttl int `json:"ttl"` Ttl int `json:"ttl"`
@ -34,11 +34,11 @@ type Record[T recordContentTypes] struct {
} }
func (*Record[T]) TableName() string { func (*Record[T]) TableName() string {
return "coredns_record" return "coredns_records"
} }
func (r *Record[T]) CheckZone() error { func (r *Record[T]) CheckZone() error {
if strings.HasSuffix(r.Zone, ".") { if !strings.HasSuffix(r.Zone, ".") {
return ErrorZoneNotEndWithDot return ErrorZoneNotEndWithDot
} }
return nil return nil
@ -65,6 +65,10 @@ func (r *Record[T]) GetType() string {
return r.RecordType return r.RecordType
} }
func (r *Record[T]) GetValue() IRecord {
return r.ToEntity()
}
type IRecord interface { type IRecord interface {
TableName() string TableName() string
CheckZone() error CheckZone() error
@ -72,4 +76,5 @@ type IRecord interface {
ToEntity() IRecord ToEntity() IRecord
FromEntity(any) error FromEntity(any) error
GetType() string GetType() string
GetValue() IRecord
} }

View File

@ -2,8 +2,6 @@ package models
import ( import (
"fmt" "fmt"
"gorm.io/gorm"
) )
const ( const (
@ -13,7 +11,7 @@ const (
) )
type Settings struct { type Settings struct {
gorm.Model ID uint `gorm:"primaryKey"`
Key string `gorm:"unique;not null;size:255"` Key string `gorm:"unique;not null;size:255"`
Value string `gorm:"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) return fmt.Sprintf("%s: %s", s.Key, s.Value)
} }
func (s *Settings) GetValue() Settings {
return *s
}
type ISettings interface { type ISettings interface {
String() string String() string
GetValue() Settings
} }

View File

@ -4,7 +4,6 @@ import (
"net/http" "net/http"
"path" "path"
"reCoreD-UI/controllers" "reCoreD-UI/controllers"
"strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
@ -24,11 +23,21 @@ func (s *Server) setupRoute() {
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("got %s:%s", username, password)
swaggerHandler := gin.New() server := s.webServer.Group(s.prefix)
swaggerHandler.GET(path.Join(swaggerPrefix, "*any"), ginSwagger.WrapHandler(swaggerfiles.Handler))
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) { metricHandler.GET(metricPrefix, func(ctx *gin.Context) {
if err := controllers.RefreshMetrics(); err != nil { if err := controllers.RefreshMetrics(); err != nil {
logrus.Error(err) logrus.Error(err)
@ -36,11 +45,18 @@ func (s *Server) setupRoute() {
promhttp.Handler().ServeHTTP(ctx.Writer, ctx.Request) promhttp.Handler().ServeHTTP(ctx.Writer, ctx.Request)
}) })
apiHandler := gin.New() apiHandler := server
groupV1 := apiHandler.Group(apiPrefix, gin.BasicAuth(gin.Accounts{ groupV1 := apiHandler.Group(apiPrefix, gin.BasicAuth(gin.Accounts{
username: password, 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 := groupV1.Group("/domains")
domains. domains.
@ -57,15 +73,15 @@ func (s *Server) setupRoute() {
PUT("/:domain", updateRecord). PUT("/:domain", updateRecord).
DELETE("/:domain/:id", deleteRecord) DELETE("/:domain/:id", deleteRecord)
server := s.webServer.Group(s.prefix) /*server := s.webServer.Group(s.prefix)
server.Use(func(ctx *gin.Context) { server.Use(apiHandler.HandleContext, metricHandler.HandleContext, func(ctx *gin.Context) {
uri := ctx.Request.RequestURI uri := ctx.Request.RequestURI
logrus.Debug(uri) logrus.Debug(uri)
switch { switch {
case strings.HasPrefix(uri, path.Join(s.prefix, apiPrefix)): case strings.HasPrefix(uri, path.Join(s.prefix, apiPrefix)):
apiHandler.HandleContext(ctx) //apiHandler.HandleContext(ctx)
case strings.HasPrefix(uri, path.Join(s.prefix, metricPrefix)): case strings.HasPrefix(uri, path.Join(s.prefix, metricPrefix)):
metricHandler.HandleContext(ctx) //metricHandler.HandleContext(ctx)
case strings.HasPrefix(uri, path.Join(s.prefix, swaggerPrefix)): case strings.HasPrefix(uri, path.Join(s.prefix, swaggerPrefix)):
if s.debug { if s.debug {
swaggerHandler.HandleContext(ctx) swaggerHandler.HandleContext(ctx)
@ -75,5 +91,8 @@ func (s *Server) setupRoute() {
default: default:
staticFileHandler()(ctx) staticFileHandler()(ctx)
} }
}) })*/
server.GET("/", staticFileHandler())
server.GET("/assets/*any", staticFileHandler())
} }

View File

@ -22,6 +22,9 @@ func NewServer(c *cli.Context) (*Server, error) {
} }
if c.Bool("debug") { if c.Bool("debug") {
database.Client = database.Client.Debug() database.Client = database.Client.Debug()
gin.SetMode(gin.DebugMode)
} else {
gin.SetMode(gin.ReleaseMode)
} }
return &Server{ return &Server{

View File

@ -91,7 +91,8 @@ export default {
dotAndMinus: 'should not start or end with "." "-"', dotAndMinus: 'should not start or end with "." "-"',
doubleDots: 'should have no contianus "."', doubleDots: 'should have no contianus "."',
logerThan63: 'should not longer than 63 characters splited by "."' logerThan63: 'should not longer than 63 characters splited by "."'
} },
tooLong: 'too long'
} }
} }
} }

View File

@ -91,7 +91,8 @@ export default {
dotAndMinus: '资源记录不能以 "."、"-" 开头或结尾', dotAndMinus: '资源记录不能以 "."、"-" 开头或结尾',
doubleDots: '资源记录不能有连续的 "."', doubleDots: '资源记录不能有连续的 "."',
logerThan63: '资源记录以 "." 分割的每个字符串长度不能超过63字符' logerThan63: '资源记录以 "." 分割的每个字符串长度不能超过63字符'
} },
tooLong: '记录值过长'
} }
} }
} }

View File

@ -35,6 +35,7 @@ export class TXTRecord {
static validate(v: TXTRecord): true | Error { static validate(v: TXTRecord): true | Error {
if (!v.text || v.text === '') return new Error(t('common.mandatory')) if (!v.text || v.text === '') return new Error(t('common.mandatory'))
if (v.text.length > 512) return new Error('records.errors.tooLong')
return true return true
} }