diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 00000000..98fa1396 --- /dev/null +++ b/build/.gitignore @@ -0,0 +1 @@ +edge-api/ \ No newline at end of file diff --git a/build/configs/api.template.yaml b/build/configs/api.template.yaml index 733fcd04..31c411dd 100644 --- a/build/configs/api.template.yaml +++ b/build/configs/api.template.yaml @@ -1,4 +1,4 @@ rpc: endpoints: [ "http://127.0.0.1:8003" ] - nodeId: "" - secret: "" \ No newline at end of file +nodeId: "" +secret: "" \ No newline at end of file diff --git a/cmd/edge-admin/main.go b/cmd/edge-admin/main.go index 7ae5ba9d..ea14cdeb 100644 --- a/cmd/edge-admin/main.go +++ b/cmd/edge-admin/main.go @@ -7,8 +7,11 @@ import ( "github.com/iwind/TeaGo" "github.com/iwind/TeaGo/Tea" _ "github.com/iwind/TeaGo/bootstrap" + "github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/sessions" + "os" + "os/exec" ) func main() { @@ -26,6 +29,16 @@ func main() { secret = "8f983f4d69b83aaa0d74b21a212f6967" } + // 启动API节点 + _, err := os.Stat(Tea.Root + "/edge-api/configs/api.yaml") + if err == nil { + logs.Println("start edge-api") + err = exec.Command(Tea.Root + "/edge-api/bin/edge-api").Start() + if err != nil { + logs.Println("[ERROR]start edge-api failed: " + err.Error()) + } + } + server := TeaGo.NewServer(false). AccessLog(false). EndAll(). diff --git a/go.mod b/go.mod index c716d2c7..4b7e5d64 100644 --- a/go.mod +++ b/go.mod @@ -6,15 +6,10 @@ replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon require ( github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000 - github.com/go-redis/redis v6.15.8+incompatible // indirect + github.com/go-sql-driver/mysql v1.5.0 github.com/go-yaml/yaml v2.1.0+incompatible - github.com/golang/protobuf v1.4.2 // indirect - github.com/iwind/TeaGo v0.0.0-20200924024009-d088df3778a6 - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 // indirect - golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa + github.com/iwind/TeaGo v0.0.0-20201010005321-430e836dee8a + golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c // indirect google.golang.org/grpc v1.32.0 - google.golang.org/protobuf v1.25.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 ) diff --git a/go.sum b/go.sum index 1b77d9d5..28d19f39 100644 --- a/go.sum +++ b/go.sum @@ -3,7 +3,6 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= -github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -20,8 +19,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= -github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -49,23 +46,12 @@ github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/iwind/TeaGo v0.0.0-20200723131229-30dff10543ad h1:EVwLRNPYoCNCinN/J9FylGBpKdCilvzUFykKtQABfNA= -github.com/iwind/TeaGo v0.0.0-20200723131229-30dff10543ad/go.mod h1:zjM7k+b+Jthhf0T0fKwuF0iy4TWb5SsU1gmKR2l+OmE= -github.com/iwind/TeaGo v0.0.0-20200816132655-f784df8e9c42 h1:X58QYxjoTstHR4sQEwx4ChAFRYtlWAfqwimQ3d2osT0= -github.com/iwind/TeaGo v0.0.0-20200816132655-f784df8e9c42/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= -github.com/iwind/TeaGo v0.0.0-20200822074248-b1cf7248c98a h1:VaWcMNOzHHT1y8MeTA2fWhG6GEfAdy6CwF2tW+KiY5Y= -github.com/iwind/TeaGo v0.0.0-20200822074248-b1cf7248c98a/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= -github.com/iwind/TeaGo v0.0.0-20200909062051-96811444bb22 h1:bCv4Emo49CZyZFbnq9lWNr8nYM1yrfhjuaM7KJEc0jk= -github.com/iwind/TeaGo v0.0.0-20200909062051-96811444bb22/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= -github.com/iwind/TeaGo v0.0.0-20200910072805-729cffe36729 h1:/v0WhSFVeNay/dA5zU9iCBXlgVDfxnztuanlauXE0gM= -github.com/iwind/TeaGo v0.0.0-20200910072805-729cffe36729/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= github.com/iwind/TeaGo v0.0.0-20200923021120-f5d76441fe9e h1:/xn7wUvlwaoA5IkdBUctv2OQbJSZ0/Dw8qRJmn55sJk= github.com/iwind/TeaGo v0.0.0-20200923021120-f5d76441fe9e/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= -github.com/iwind/TeaGo v0.0.0-20200924024009-d088df3778a6 h1:7OZC/Qy7Z/hK9vG6YQOwHNOUPunSImYYJMiIfvuDQZ0= -github.com/iwind/TeaGo v0.0.0-20200924024009-d088df3778a6/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= +github.com/iwind/TeaGo v0.0.0-20201010005321-430e836dee8a h1:sO6uDbQOEe6/tIB3o31vn6eD/JmkKGErKgcOA/Cpb+Q= +github.com/iwind/TeaGo v0.0.0-20201010005321-430e836dee8a/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= @@ -89,14 +75,9 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.12.0/go.mod h1:fUqqXB5vEgVCZ131L+9say31RAri6aF6KDViawhxKK8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20= -github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/shirou/gopsutil v2.20.7+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= @@ -104,7 +85,6 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= @@ -145,6 +125,8 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c= +golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= diff --git a/internal/configs/api_config.go b/internal/configs/api_config.go index 5c89cc56..63f8ec88 100644 --- a/internal/configs/api_config.go +++ b/internal/configs/api_config.go @@ -6,6 +6,7 @@ import ( "io/ioutil" ) +// API配置 type APIConfig struct { RPC struct { Endpoints []string `yaml:"endpoints"` @@ -14,6 +15,7 @@ type APIConfig struct { Secret string `yaml:"secret"` } +// 加载API配置 func LoadAPIConfig() (*APIConfig, error) { data, err := ioutil.ReadFile(Tea.ConfigFile("api.yaml")) if err != nil { @@ -28,3 +30,12 @@ func LoadAPIConfig() (*APIConfig, error) { return config, nil } + +// 写入API配置 +func (this *APIConfig) WriteFile(path string) error { + data, err := yaml.Marshal(this) + if err != nil { + return err + } + return ioutil.WriteFile(path, data, 0666) +} diff --git a/internal/rpc/rpc_client.go b/internal/rpc/rpc_client.go index abed49bf..961c5074 100644 --- a/internal/rpc/rpc_client.go +++ b/internal/rpc/rpc_client.go @@ -175,7 +175,7 @@ func (this *RPCClient) SysSettingRPC() pb.SysSettingServiceClient { return pb.NewSysSettingServiceClient(this.pickConn()) } -// 构造上下文 +// 构造Admin上下文 func (this *RPCClient) Context(adminId int64) context.Context { ctx := context.Background() m := maps.Map{ @@ -198,6 +198,29 @@ func (this *RPCClient) Context(adminId int64) context.Context { return ctx } +// 构造API上下文 +func (this *RPCClient) APIContext(apiNodeId int64) context.Context { + ctx := context.Background() + m := maps.Map{ + "timestamp": time.Now().Unix(), + "type": "api", + "userId": apiNodeId, + } + method, err := encrypt.NewMethodInstance(teaconst.EncryptMethod, this.apiConfig.Secret, this.apiConfig.NodeId) + if err != nil { + utils.PrintError(err) + return context.Background() + } + data, err := method.Encrypt(m.AsJSON()) + if err != nil { + utils.PrintError(err) + return context.Background() + } + token := base64.StdEncoding.EncodeToString(data) + ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token) + return ctx +} + // 随机选择一个连接 func (this *RPCClient) pickConn() *grpc.ClientConn { if len(this.conns) == 0 { diff --git a/internal/setup/utils.go b/internal/setup/utils.go new file mode 100644 index 00000000..ff16b014 --- /dev/null +++ b/internal/setup/utils.go @@ -0,0 +1,18 @@ +package setup + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/configs" +) + +var isConfigured bool + +// 判断系统是否已经配置过 +func IsConfigured() bool { + if isConfigured { + return true + } + + _, err := configs.LoadAPIConfig() + isConfigured = err == nil + return isConfigured +} diff --git a/internal/web/actions/default/index/index.go b/internal/web/actions/default/index/index.go index bccb2a21..b5c8c3d5 100644 --- a/internal/web/actions/default/index/index.go +++ b/internal/web/actions/default/index/index.go @@ -4,10 +4,11 @@ import ( "fmt" "github.com/TeaOSLab/EdgeAdmin/internal/oplogs" "github.com/TeaOSLab/EdgeAdmin/internal/rpc" - "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/TeaOSLab/EdgeAdmin/internal/setup" "github.com/TeaOSLab/EdgeAdmin/internal/utils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/helpers" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/types" stringutil "github.com/iwind/TeaGo/utils/string" @@ -25,6 +26,12 @@ func (this *IndexAction) RunGet(params struct { Auth *helpers.UserShouldAuth }) { + // 检查系统是否已经配置过 + if !setup.IsConfigured() { + this.RedirectURL("/setup") + return + } + // 已登录跳转到dashboard if params.Auth.IsUser() { this.RedirectURL("/dashboard") diff --git a/internal/web/actions/default/settings/database/index.go b/internal/web/actions/default/settings/database/index.go index 20bd43ef..a82cba08 100644 --- a/internal/web/actions/default/settings/database/index.go +++ b/internal/web/actions/default/settings/database/index.go @@ -41,16 +41,15 @@ func (this *IndexAction) RunGet(params struct{}) { } if config.DBs == nil { - this.Data["error"] = "can not find valid database config: api_db.yaml" + this.Data["error"] = "no database configured in config file: api_db.yaml" this.Show() return } - dbConfig, ok := config.DBs[config.Default.DB] - if !ok { - this.Data["error"] = "can not find valid database config: api_db.yaml" - this.Show() - return + var dbConfig *dbs.DBConfig + for _, db := range config.DBs { + dbConfig = db + break } dsn := dbConfig.Dsn dsn = regexp.MustCompile(`tcp\((.+)\)`).ReplaceAllString(dsn, "$1") diff --git a/internal/web/actions/default/settings/database/update.go b/internal/web/actions/default/settings/database/update.go index a6b294ae..79de6051 100644 --- a/internal/web/actions/default/settings/database/update.go +++ b/internal/web/actions/default/settings/database/update.go @@ -50,11 +50,12 @@ func (this *UpdateAction) RunGet(params struct{}) { return } - dbConfig, ok := config.DBs[config.Default.DB] - if !ok { - this.Show() - return + var dbConfig *dbs.DBConfig + for _, db := range config.DBs { + dbConfig = db + break } + dsn := dbConfig.Dsn dsn = regexp.MustCompile(`tcp\((.+)\)`).ReplaceAllString(dsn, "$1") dsnURL, err := url.Parse("mysql://" + dsn) diff --git a/internal/web/actions/default/setup/helper.go b/internal/web/actions/default/setup/helper.go new file mode 100644 index 00000000..59116ab6 --- /dev/null +++ b/internal/web/actions/default/setup/helper.go @@ -0,0 +1,17 @@ +package setup + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/setup" + "github.com/iwind/TeaGo/actions" +) + +type Helper struct { +} + +func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) { + if setup.IsConfigured() { + actionPtr.Object().RedirectURL("/") + return false + } + return true +} diff --git a/internal/web/actions/default/setup/index.go b/internal/web/actions/default/setup/index.go new file mode 100644 index 00000000..f503aec0 --- /dev/null +++ b/internal/web/actions/default/setup/index.go @@ -0,0 +1,46 @@ +package setup + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "net" + "regexp" + "sort" +) + +type IndexAction struct { + actionutils.ParentAction +} + +func (this *IndexAction) Init() { + this.Nav("", "", "") +} + +func (this *IndexAction) RunGet(params struct{}) { + // 当前服务器的IP + serverIPs := []string{} + addrs, _ := net.InterfaceAddrs() + for _, addr := range addrs { + netAddr, ok := addr.(*net.IPNet) + if !ok { + continue + } + + serverIPs = append(serverIPs, netAddr.IP.String()) + } + + // 对IP进行排序,我们希望IPv4排在前面,而且希望127.0.0.1排在IPv4里的最后 + sort.Slice(serverIPs, func(i, j int) bool { + ip1 := serverIPs[i] + + if ip1 == "127.0.0.1" { + return false + } + if regexp.MustCompile(`^\d+\.\d+\.\d+.\d+$`).MatchString(ip1) { + return true + } + return false + }) + this.Data["serverIPs"] = serverIPs + + this.Show() +} diff --git a/internal/web/actions/default/setup/init.go b/internal/web/actions/default/setup/init.go new file mode 100644 index 00000000..faa03530 --- /dev/null +++ b/internal/web/actions/default/setup/init.go @@ -0,0 +1,17 @@ +package setup + +import "github.com/iwind/TeaGo" + +func init() { + TeaGo.BeforeStart(func(server *TeaGo.Server) { + server. + Helper(new(Helper)). + Prefix("/setup"). + Get("", new(IndexAction)). + Post("/validateApi", new(ValidateApiAction)). + Post("/validateDb", new(ValidateDbAction)). + Post("/validateAdmin", new(ValidateAdminAction)). + Post("/install", new(InstallAction)). + EndAll() + }) +} diff --git a/internal/web/actions/default/setup/install.go b/internal/web/actions/default/setup/install.go new file mode 100644 index 00000000..0b346f2a --- /dev/null +++ b/internal/web/actions/default/setup/install.go @@ -0,0 +1,203 @@ +package setup + +import ( + "bytes" + "encoding/json" + "github.com/TeaOSLab/EdgeAdmin/internal/configs" + "github.com/TeaOSLab/EdgeAdmin/internal/rpc" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/go-yaml/yaml" + "github.com/iwind/TeaGo/Tea" + "github.com/iwind/TeaGo/actions" + "github.com/iwind/TeaGo/dbs" + "github.com/iwind/TeaGo/maps" + "io/ioutil" + "os" + "os/exec" + "time" +) + +type InstallAction struct { + actionutils.ParentAction +} + +func (this *InstallAction) RunPost(params struct { + ApiNodeJSON []byte + DbJSON []byte + AdminJSON []byte + + Must *actions.Must +}) { + // API节点配置 + apiNodeMap := maps.Map{} + err := json.Unmarshal(params.ApiNodeJSON, &apiNodeMap) + if err != nil { + this.Fail("API节点配置数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error()) + } + + // 数据库 + dbMap := maps.Map{} + err = json.Unmarshal(params.DbJSON, &dbMap) + if err != nil { + this.Fail("数据库配置数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error()) + } + + // 管理员 + adminMap := maps.Map{} + err = json.Unmarshal(params.AdminJSON, &adminMap) + if err != nil { + this.Fail("管理员数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error()) + } + + // 安装API节点 + mode := apiNodeMap.GetString("mode") + if mode == "new" { + // 整个系统目录结构为: + // edge-admin/ + // edge-api/ + // bin/ + // ... + + // 检查环境 + apiNodeDir := Tea.Root + "/edge-api" + for _, dir := range []string{"edge-api", "edge-api/configs", "edge-api/bin"} { + apiNodeDir := Tea.Root + "/" + dir + _, err = os.Stat(apiNodeDir) + if err != nil { + if os.IsNotExist(err) { + this.Fail("在当前目录下找不到" + dir + "目录,请将" + dir + "目录上传或者重新下载解压") + } + this.Fail("无法检查" + dir + "目录,发生错误:" + err.Error()) + } + } + + // 保存数据库配置 + dsn := dbMap.GetString("username") + ":" + dbMap.GetString("password") + "@tcp(" + dbMap.GetString("host") + ":" + dbMap.GetString("port") + ")/" + dbMap.GetString("database") + "?charset=utf8mb4&timeout=30s" + dbConfig := &dbs.Config{ + DBs: map[string]*dbs.DBConfig{ + "prod": { + Driver: "mysql", + Dsn: dsn, + Prefix: "edge", + }}, + } + dbConfigData, err := yaml.Marshal(dbConfig) + if err != nil { + this.Fail("生成数据库配置失败:" + err.Error()) + } + err = ioutil.WriteFile(apiNodeDir+"/configs/db.yaml", dbConfigData, 0666) + if err != nil { + this.Fail("保存数据库配置失败:" + err.Error()) + } + + err = ioutil.WriteFile(Tea.ConfigFile("/api_db.yaml"), dbConfigData, 0666) + if err != nil { + this.Fail("保存数据库配置失败:" + err.Error()) + } + + // 开始安装 + var resultMap = maps.Map{} + { + cmd := exec.Command(apiNodeDir+"/bin/edge-api", "setup", "-api-node-protocol=http", "-api-node-host=\""+apiNodeMap.GetString("newHost")+"\"", "-api-node-port=\""+apiNodeMap.GetString("newPort")+"\"") + output := bytes.NewBuffer([]byte{}) + cmd.Stdout = output + err = cmd.Run() + if err != nil { + this.Fail("安装失败:" + err.Error()) + } + + resultData := output.Bytes() + err = json.Unmarshal(resultData, &resultMap) + if err != nil { + this.Fail("安装节点时返回数据错误:" + err.Error() + "(" + string(resultData) + ")") + } + if !resultMap.GetBool("isOk") { + this.Fail("节点安装错误:" + resultMap.GetString("error")) + } + } + + // 启动API节点 + { + cmd := exec.Command(apiNodeDir + "/bin/edge-api") + err = cmd.Start() + if err != nil { + this.Fail("API节点启动失败:" + err.Error()) + } + } + + // 写入API节点配置,完成安装 + apiConfig := &configs.APIConfig{ + RPC: struct { + Endpoints []string `yaml:"endpoints"` + }{ + Endpoints: []string{"http://" + apiNodeMap.GetString("newHost") + ":" + apiNodeMap.GetString("newPort")}, + }, + NodeId: resultMap.GetString("adminNodeId"), + Secret: resultMap.GetString("adminNodeSecret"), + } + + // 设置管理员 + client, err := rpc.NewRPCClient(apiConfig) + if err != nil { + this.FailField("oldHost", "测试API节点时出错,请检查配置,错误信息:"+err.Error()) + } + ctx := client.Context(0) + for i := 0; i < 3; i++ { + _, err = client.AdminRPC().CreateOrUpdateAdmin(ctx, &pb.CreateOrUpdateAdminRequest{ + Username: adminMap.GetString("username"), + Password: adminMap.GetString("password"), + }) + // 这里我们尝试多次是为了当代API节点启动完毕 + if err != nil { + time.Sleep(1 * time.Second) + } + } + if err != nil { + this.Fail("设置管理员账号出错:" + err.Error()) + } + + err = apiConfig.WriteFile(Tea.ConfigFile("api.yaml")) + if err != nil { + this.Fail("保存配置失败,原因:" + err.Error()) + } + + this.Success() + } else if mode == "old" { + // 构造RPC + apiConfig := &configs.APIConfig{ + RPC: struct { + Endpoints []string `yaml:"endpoints"` + }{ + Endpoints: []string{apiNodeMap.GetString("oldProtocol") + "://" + apiNodeMap.GetString("oldHost") + ":" + apiNodeMap.GetString("oldPort")}, + }, + NodeId: apiNodeMap.GetString("oldNodeId"), + Secret: apiNodeMap.GetString("oldNodeSecret"), + } + client, err := rpc.NewRPCClient(apiConfig) + if err != nil { + this.FailField("oldHost", "测试API节点时出错,请检查配置,错误信息:"+err.Error()) + } + + // 设置管理员 + ctx := client.APIContext(0) + _, err = client.AdminRPC().CreateOrUpdateAdmin(ctx, &pb.CreateOrUpdateAdminRequest{ + Username: adminMap.GetString("username"), + Password: adminMap.GetString("password"), + }) + if err != nil { + this.Fail("设置管理员账号出错:" + err.Error()) + } + + // 写入API节点配置,完成安装 + err = apiConfig.WriteFile(Tea.ConfigFile("api.yaml")) + if err != nil { + this.Fail("保存配置失败,原因:" + err.Error()) + } + + // 成功 + this.Success() + } else { + this.Fail("错误的API节点模式:'" + mode + "'") + } +} diff --git a/internal/web/actions/default/setup/validateAdmin.go b/internal/web/actions/default/setup/validateAdmin.go new file mode 100644 index 00000000..ba10088b --- /dev/null +++ b/internal/web/actions/default/setup/validateAdmin.go @@ -0,0 +1,38 @@ +package setup + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/iwind/TeaGo/actions" + "github.com/iwind/TeaGo/maps" + "strings" +) + +type ValidateAdminAction struct { + actionutils.ParentAction +} + +func (this *ValidateAdminAction) RunPost(params struct { + AdminUsername string + AdminPassword string + AdminPassword2 string + Must *actions.Must +}) { + params.Must. + Field("adminUsername", params.AdminUsername). + Require("请输入登录用户名"). + Match(`^[a-zA-Z0-9_]+$`, "用户名中只能包含英文、数字或下划线"). + Field("adminPassword", params.AdminPassword). + Require("请输入登录密码"). + Match(`^[a-zA-Z0-9_]+$`, "密码中只能包含英文、数字或下划线"). + Field("adminPassword2", params.AdminPassword2). + Require("请输入确认密码"). + Equal(params.AdminPassword, "两次输入的密码不一致") + + this.Data["admin"] = maps.Map{ + "username": params.AdminUsername, + "password": params.AdminPassword, + "passwordMask": strings.Repeat("*", len(params.AdminPassword)), + } + + this.Success() +} diff --git a/internal/web/actions/default/setup/validateApi.go b/internal/web/actions/default/setup/validateApi.go new file mode 100644 index 00000000..02e5d461 --- /dev/null +++ b/internal/web/actions/default/setup/validateApi.go @@ -0,0 +1,105 @@ +package setup + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/configs" + "github.com/TeaOSLab/EdgeAdmin/internal/rpc" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/actions" + "github.com/iwind/TeaGo/maps" + "github.com/iwind/TeaGo/types" + "net" + "strings" +) + +type ValidateApiAction struct { + actionutils.ParentAction +} + +func (this *ValidateApiAction) RunPost(params struct { + Mode string + NewPort string + NewHost string + OldProtocol string + OldHost string + OldPort string + OldNodeId string + OldNodeSecret string + + Must *actions.Must +}) { + params.OldNodeId = strings.Trim(params.OldNodeId, "\"' ") + params.OldNodeSecret = strings.Trim(params.OldNodeSecret, "\"' ") + + this.Data["apiNode"] = maps.Map{ + "mode": params.Mode, + + "newPort": params.NewPort, + "newHost": params.NewHost, + + "oldProtocol": params.OldProtocol, + "oldHost": params.OldHost, + "oldPort": params.OldPort, + "oldNodeId": params.OldNodeId, + "oldNodeSecret": params.OldNodeSecret, + } + + if params.Mode == "new" { + params.Must. + Field("newPort", params.NewPort). + Require("请输入节点端口"). + Match(`^\d+$`, "节点端口只能是数字"). + MinLength(4, "请输入4位以上的数字"). + MaxLength(5, "请输入5位以下的数字") + newPort := types.Int(params.NewPort) + if newPort < 1024 { + this.FailField("newPort", "端口号不能小于1024") + } + if newPort > 65534 { + this.FailField("newPort", "端口号不能大于65534") + } + + conn, err := net.Dial("tcp", "127.0.0.1:"+params.NewPort) + if err == nil { + _ = conn.Close() + this.FailField("newPort", "此端口已经被别的服务占用,请换一个") + } + + params.Must. + Field("newHost", params.NewHost). + Require("请输入节点主机地址") + + this.Success() + return + } + + // 使用已有的API节点 + params.Must. + Field("oldHost", params.OldHost). + Require("请输入主机地址"). + Field("oldPort", params.OldPort). + Require("请输入服务端口"). + Match(`^\d+$`, "服务端口只能是数字"). + Field("oldNodeId", params.OldNodeId). + Require("请输入节点nodeId"). + Field("oldNodeSecret", params.OldNodeSecret). + Require("请输入节点secret") + client, err := rpc.NewRPCClient(&configs.APIConfig{ + RPC: struct { + Endpoints []string `yaml:"endpoints"` + }{ + Endpoints: []string{params.OldProtocol + "://" + params.OldHost + ":" + params.OldPort}, + }, + NodeId: params.OldNodeId, + Secret: params.OldNodeSecret, + }) + if err != nil { + this.FailField("oldHost", "测试API节点时出错,请检查配置,错误信息:"+err.Error()) + } + _, err = client.APINodeRPC().FindCurrentAPINodeVersion(client.APIContext(0), &pb.FindCurrentAPINodeVersionRequest{}) + if err != nil { + this.FailField("oldHost", "无法连接此API节点,错误信息:"+err.Error()) + } + + this.Success() +} diff --git a/internal/web/actions/default/setup/validateDb.go b/internal/web/actions/default/setup/validateDb.go new file mode 100644 index 00000000..da078462 --- /dev/null +++ b/internal/web/actions/default/setup/validateDb.go @@ -0,0 +1,78 @@ +package setup + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + _ "github.com/go-sql-driver/mysql" + "github.com/iwind/TeaGo/actions" + "github.com/iwind/TeaGo/dbs" + "github.com/iwind/TeaGo/maps" + "strings" +) + +type ValidateDbAction struct { + actionutils.ParentAction +} + +func (this *ValidateDbAction) RunPost(params struct { + Host string + Port string + Database string + Username string + Password string + + Must *actions.Must +}) { + params.Must. + Field("host", params.Host). + Require("请输入主机地址"). + Match(`^[\w\.-]+$`, "主机地址中不能包含特殊字符"). + Field("port", params.Port). + Require("请输入端口"). + Match(`^\d+$`, "端口中只能包含数字"). + Field("database", params.Database). + Require("请输入数据库名称"). + Match(`^[\w\.-]+$`, "数据库名称中不能包含特殊字符"). + Field("username", params.Username). + Require("请输入连接数据库的用户名"). + Match(`^[\w\.-]+$`, "用户名中不能包含特殊字符") + + // 测试连接 + db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{ + Driver: "mysql", + Dsn: params.Username + ":" + params.Password + "@tcp(" + params.Host + ":" + params.Port + ")/" + params.Database, + Prefix: "", + }) + if err != nil { + this.Fail("数据库信息错误:" + err.Error()) + } + + err = db.Raw().Ping() + if err != nil { + // 是否是数据库不存在 + if strings.Contains(err.Error(), "Error 1049") { + db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{ + Driver: "mysql", + Dsn: params.Username + ":" + params.Password + "@tcp(" + params.Host + ":" + params.Port + ")/", + Prefix: "", + }) + + _, err = db.Exec("CREATE DATABASE `" + params.Database + "`") + if err != nil { + this.Fail("尝试创建数据库失败:" + err.Error()) + } + } else { + this.Fail("无法连接到数据库,请检查配置:" + err.Error()) + } + } + + this.Data["db"] = maps.Map{ + "host": params.Host, + "port": params.Port, + "database": params.Database, + "username": params.Username, + "password": params.Password, + "passwordMask": strings.Repeat("*", len(params.Password)), + } + + this.Success() +} diff --git a/internal/web/actions/default/ui/init.go b/internal/web/actions/default/ui/init.go index 8b5b0bfa..ea127400 100644 --- a/internal/web/actions/default/ui/init.go +++ b/internal/web/actions/default/ui/init.go @@ -1,6 +1,7 @@ package ui import ( + "compress/gzip" "github.com/iwind/TeaGo" "github.com/iwind/TeaGo/actions" ) @@ -12,7 +13,7 @@ func init() { Get("/download", new(DownloadAction)). // 以下的需要压缩 - Helper(new(actions.Gzip)). + Helper(&actions.Gzip{Level: gzip.BestCompression}). Get("/components.js", new(ComponentsAction)). EndAll() }) diff --git a/internal/web/helpers/user_must_auth.go b/internal/web/helpers/user_must_auth.go index f3449db1..a9cf3890 100644 --- a/internal/web/helpers/user_must_auth.go +++ b/internal/web/helpers/user_must_auth.go @@ -3,6 +3,7 @@ package helpers import ( teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const" nodes "github.com/TeaOSLab/EdgeAdmin/internal/rpc" + "github.com/TeaOSLab/EdgeAdmin/internal/setup" "github.com/TeaOSLab/EdgeAdmin/internal/utils" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/iwind/TeaGo/actions" @@ -23,6 +24,12 @@ func NewUserMustAuth() *UserMustAuth { func (this *UserMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramName string) (goNext bool) { var action = actionPtr.Object() + // 检查系统是否已经配置过 + if !setup.IsConfigured() { + action.RedirectURL("/setup") + return + } + var session = action.Session() var adminId = session.GetInt("adminId") if adminId <= 0 { @@ -33,6 +40,7 @@ func (this *UserMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam // 检查用户是否存在 rpc, err := nodes.SharedRPC() if err != nil { + action.WriteString("setup rpc error: " + err.Error()) utils.PrintError(err) return false } @@ -40,7 +48,7 @@ func (this *UserMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam rpcResp, err := rpc.AdminRPC().CheckAdminExists(rpc.Context(0), &pb.CheckAdminExistsRequest{AdminId: int64(adminId)}) if err != nil { utils.PrintError(err) - actionPtr.Object().WriteString(teaconst.ErrServer) + action.WriteString(teaconst.ErrServer) return false } diff --git a/internal/web/import.go b/internal/web/import.go index 044ad8b1..f7dc6cd3 100644 --- a/internal/web/import.go +++ b/internal/web/import.go @@ -71,5 +71,6 @@ import ( _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/profile" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/security" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/upgrade" + _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ui" ) diff --git a/web/public/js/utils.js b/web/public/js/utils.js index 09f6e636..dec98012 100644 --- a/web/public/js/utils.js +++ b/web/public/js/utils.js @@ -138,8 +138,8 @@ window.teaweb = { if (message.length > 30) { width = "30em"; } - Swal.fire({ - text: message, + + let config = { confirmButtonText: "确定", buttonsStyling: false, icon: "success", @@ -154,9 +154,19 @@ window.teaweb = { setTimeout(function () { callback(); }); + } else if (typeof (callback) == "string") { + window.location = callback } } - }); + } + + if (message.startsWith("html:")) { + config.html = message.substring(5) + } else { + config.text = message + } + + Swal.fire(config); }, successToast: function (message, timeout) { if (timeout == null) { diff --git a/web/views/@default/@layout.css b/web/views/@default/@layout.css index 6c4ebaae..3174e6f5 100644 --- a/web/views/@default/@layout.css +++ b/web/views/@default/@layout.css @@ -130,7 +130,7 @@ p.comment, div.comment { color: rgba(0, 0, 0, 0.3); padding-top: 0.4em; - font-size: 0.9em; + font-size: 1em; } p.comment em, div.comment em { diff --git a/web/views/@default/@layout.css.map b/web/views/@default/@layout.css.map index 9197ec34..54cbaebb 100644 --- a/web/views/@default/@layout.css.map +++ b/web/views/@default/@layout.css.map @@ -1 +1 @@ -{"version":3,"sources":["@left_menu.less","@layout.less"],"names":[],"mappings":"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","file":"@layout.css"} \ No newline at end of file +{"version":3,"sources":["@left_menu.less","@layout.less"],"names":[],"mappings":"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","file":"@layout.css"} \ No newline at end of file diff --git a/web/views/@default/@layout.html b/web/views/@default/@layout.html index bb733aa8..ec185e20 100644 --- a/web/views/@default/@layout.html +++ b/web/views/@default/@layout.html @@ -23,7 +23,7 @@