├── .gitignore
├── .travis.yml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── build
├── build.go
├── build_test.go
├── repo.go
└── task.go
├── config.go
├── db.go
├── deploy
├── deploy.go
├── server.go
└── task.go
├── docs
├── .nojekyll
├── 2.0
│ └── .nojekyll
├── assets
│ ├── css
│ │ └── app.css
│ └── img
│ │ ├── favicon.ico
│ │ ├── gitee-logo-black.svg
│ │ ├── logo-blue.svg
│ │ ├── logo.svg
│ │ └── wechat-donate.png
├── docs
│ ├── _404.md
│ ├── _sidebar.md
│ ├── architecture.md
│ ├── assets
│ │ ├── css
│ │ │ └── app.css
│ │ └── img
│ │ │ ├── syncd-member-add.png
│ │ │ ├── syncd-principle.png
│ │ │ ├── syncd-project-add.png
│ │ │ ├── syncd-server-add.png
│ │ │ ├── syncd-server-group-add.png
│ │ │ └── syncd-space-add.png
│ ├── build.md
│ ├── donate.md
│ ├── help.md
│ ├── home.md
│ ├── include
│ │ └── footer.md
│ ├── index.html
│ ├── install.md
│ ├── project.md
│ ├── server.md
│ ├── setting.md
│ ├── troubleinstall.md
│ └── troubleuse.md
├── index.html
└── install.sh
├── go.mod
├── go.sum
├── model
├── deploy_apply.go
├── deploy_build.go
├── deploy_task.go
├── model.go
├── project.go
├── project_member.go
├── project_space.go
├── server.go
├── server_group.go
├── user.go
├── user_role.go
└── user_token.go
├── module
├── deploy
│ ├── apply.go
│ ├── build.go
│ └── deploy.go
├── project
│ ├── member.go
│ ├── project.go
│ └── space.go
├── server
│ ├── group.go
│ └── server.go
└── user
│ ├── login.go
│ ├── priv.go
│ ├── role.go
│ ├── token.go
│ └── user.go
├── public
├── css
│ ├── app.d770df8f.css
│ ├── chunk-21c9e7bf.a3c25090.css
│ ├── chunk-8a44c3e0.cb38e73a.css
│ ├── chunk-e3695f98.b86ea360.css
│ └── chunk-vendors.9c8e3772.css
├── favicon.ico
├── fonts
│ ├── element-icons.2fad952a.woff
│ └── element-icons.6f0a7632.ttf
├── img
│ └── login_bg.193d505f.jpg
├── index.html
└── js
│ ├── app.2bf0ccd2.js
│ ├── app.2bf0ccd2.js.map
│ ├── chunk-162ff1ff.15a23fae.js
│ ├── chunk-162ff1ff.15a23fae.js.map
│ ├── chunk-21c9e7bf.ec2f8115.js
│ ├── chunk-21c9e7bf.ec2f8115.js.map
│ ├── chunk-22f331f4.0eddfbce.js
│ ├── chunk-22f331f4.0eddfbce.js.map
│ ├── chunk-232f6874.c45db812.js
│ ├── chunk-232f6874.c45db812.js.map
│ ├── chunk-29553eaa.4ebf8cb5.js
│ ├── chunk-29553eaa.4ebf8cb5.js.map
│ ├── chunk-2d0f0051.d23388af.js
│ ├── chunk-2d0f0051.d23388af.js.map
│ ├── chunk-58cbcdef.2c344c6b.js
│ ├── chunk-58cbcdef.2c344c6b.js.map
│ ├── chunk-5a77cfd8.4e40248e.js
│ ├── chunk-5a77cfd8.4e40248e.js.map
│ ├── chunk-768b7ab8.2e39c37f.js
│ ├── chunk-768b7ab8.2e39c37f.js.map
│ ├── chunk-7ef915ac.e5c53307.js
│ ├── chunk-7ef915ac.e5c53307.js.map
│ ├── chunk-8a44c3e0.5374e90e.js
│ ├── chunk-8a44c3e0.5374e90e.js.map
│ ├── chunk-a101e242.0f232132.js
│ ├── chunk-a101e242.0f232132.js.map
│ ├── chunk-e3695f98.4489b617.js
│ ├── chunk-e3695f98.4489b617.js.map
│ ├── chunk-f4b66f66.eb08a10d.js
│ ├── chunk-f4b66f66.eb08a10d.js.map
│ ├── chunk-vendors.512af33a.js
│ └── chunk-vendors.512af33a.js.map
├── render
└── render.go
├── resource
└── sql
│ └── syncd_v2.0.0.sql
├── router
├── common
│ ├── hook.go
│ └── inspace.go
├── deploy
│ ├── apply.go
│ ├── build.go
│ ├── deploy.go
│ └── mail.go
├── middleware
│ └── api_priv.go
├── project
│ ├── member.go
│ ├── project.go
│ └── space.go
├── route
│ ├── api
│ │ └── api.go
│ └── route.go
├── server
│ ├── group.go
│ └── server.go
└── user
│ ├── login.go
│ ├── my.go
│ ├── role.go
│ └── user.go
├── script
└── install.sh
├── sendmail.go
├── syncd.go
├── syncd.ini
├── syncd
└── main.go
├── util
├── command
│ ├── command.go
│ ├── command_test.go
│ ├── task.go
│ └── task_test.go
├── goaes
│ └── aes.go
├── gofile
│ └── file.go
├── gois
│ └── is.go
├── golog
│ ├── file.go
│ └── log.go
├── gopath
│ └── path.go
├── goslice
│ └── slice.go
└── gostring
│ └── string.go
└── web
├── .browserslistrc
├── .gitignore
├── README.md
├── babel.config.js
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── api
│ ├── deploy.js
│ ├── login.js
│ ├── project.js
│ ├── server.js
│ └── user.js
├── asset
│ ├── login_bg.jpg
│ ├── logo.png
│ └── logo.svg
├── component
│ └── ScrollBar
│ │ └── index.vue
├── lang
│ ├── en.js
│ ├── index.js
│ └── zh_cn.js
├── lib
│ ├── code.js
│ ├── data.js
│ ├── fetch.js
│ ├── priv.js
│ └── util.js
├── main.js
├── router
│ └── index.js
├── scss
│ └── app.scss
├── store
│ ├── index.js
│ └── module
│ │ └── account.js
└── view
│ ├── Dashboard.vue
│ ├── Layer.vue
│ ├── Login.vue
│ ├── deploy
│ ├── Apply.vue
│ ├── Deploy.vue
│ └── Release.vue
│ ├── project
│ ├── Member.vue
│ ├── Project.vue
│ └── Space.vue
│ ├── server
│ ├── Group.vue
│ └── Server.vue
│ └── user
│ ├── Group.vue
│ └── User.vue
├── vue.config.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, build with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # dir
15 | .DS_Store
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | sudo: false
3 | go:
4 | - 1.11.x
5 |
6 | script: go get github.com/dreamans/syncd/syncd
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.12-alpine3.10 AS build
2 | COPY . /usr/local/src
3 | WORKDIR /usr/local/src
4 | RUN apk --no-cache add build-base && make
5 |
6 | FROM alpine:3.10
7 | WORKDIR /syncd
8 | COPY --from=build /usr/local/src/output /syncd
9 | EXPOSE 8878
10 | CMD [ "/syncd/bin/syncd" ]
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 syncd
4 | Copyright (c) 2018 dreamans
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | GO_CMD=go
2 | SYNCD_BIN=syncd_bin
3 | SYNCD_BIN_PATH=./output/bin
4 | SYNCD_ETC_PATH=./output/etc
5 | SYNCD_PUBLIC_PATH=./output/public
6 | SYNCD_LOG_PATH=./output/log
7 | SYNCD_RES_PATH=./output/resource
8 |
9 | .PHONY: all
10 | all: clean build install
11 |
12 | .PHONY: linux
13 | linux: clean build-linux install
14 |
15 | .PHONY: build
16 | build:
17 | @echo "build syncd start >>>"
18 | GOPROXY=https://goproxy.io $(GO_CMD) mod tidy
19 | $(GO_CMD) build -o $(SYNCD_BIN) ./syncd/main.go
20 | @echo ">>> build syncd complete"
21 |
22 | .PHONY: install
23 | install:
24 | @echo "install syncd start >>>"
25 | mkdir -p $(SYNCD_BIN_PATH)
26 | mv $(SYNCD_BIN) $(SYNCD_BIN_PATH)/syncd
27 | mkdir -p $(SYNCD_ETC_PATH)
28 | cp ./syncd.ini $(SYNCD_ETC_PATH)
29 | cp -r ./public $(SYNCD_PUBLIC_PATH)
30 | mkdir -p $(SYNCD_LOG_PATH)
31 | cp -r ./resource $(SYNCD_RES_PATH)
32 | @echo ">>> install syncd complete"
33 |
34 | .PHONY: clean
35 | clean:
36 | @echo "clean start >>>"
37 | rm -fr ./output
38 | rm -f $(SYNCD_BIN)
39 | @echo ">>> clean complete"
40 |
41 | .PHONY: build-linux
42 | build-linux:
43 | @echo "build-linux start >>>"
44 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO_CMD) build -o $(SYNCD_BIN) ./syncd/main.go
45 | @echo ">>> build-linux complete"
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Syncd - 自动化部署工具
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Syncd是一款开源的代码部署工具,它具有简单、高效、易用等特点,可以提高团队的工作效率。
15 | 
16 | 码云GVP项目
17 |
18 | ## 文档
19 |
20 | [查看文档](https://syncd.cc)
21 |
22 | ## 特性
23 |
24 | - Go语言开发,编译简单、运行高效
25 | - Web界面访问,交互友好
26 | - 权限模型灵活自由
27 | - 支持自定义构建
28 | - 支持Git仓库
29 | - 支持分支、Tag上线
30 | - 部署Hook支持,可扩展性强
31 | - 完善的上线工作流
32 | - 邮件通知机制
33 |
34 | ## 项目地址
35 |
36 | Github: https://github.com/dreamans/syncd
37 |
38 | Gitee: https://gitee.com/dreamans/syncd
39 |
40 | ## 获取帮助
41 |
42 | Syncd使用交流QQ群①: 725302833
43 |
44 | ## 捐赠
45 |
46 | Syncd的发展离不开您的支持,[前往捐赠](https://syncd.cc/docs/#donate.md)
47 |
48 | ## LICENSE
49 |
50 | 本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 LICENSE 文件中。
--------------------------------------------------------------------------------
/build/build.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package build
6 |
7 | import (
8 | "time"
9 | "fmt"
10 |
11 | "github.com/dreamans/syncd/util/gostring"
12 | "github.com/dreamans/syncd/util/command"
13 | "github.com/dreamans/syncd/util/gofile"
14 | "github.com/dreamans/syncd/util/gopath"
15 | )
16 |
17 | type Build struct {
18 | repo *Repo
19 | local string
20 | tmp string
21 | packFile string
22 | scriptFile string
23 | task *command.Task
24 | result *Result
25 | }
26 |
27 | const (
28 | STATUS_INIT = 1
29 | STATUS_ING = 2
30 | STATUS_DONE = 3
31 | STATUS_FAILED = 4
32 | )
33 |
34 | const (
35 | COMMAND_TIMEOUT = 86400
36 | )
37 |
38 | func NewBuild(repo *Repo, local, tmp, packFile, scripts string) (*Build, error) {
39 | build := &Build{
40 | repo: repo,
41 | local: local,
42 | tmp: tmp,
43 | packFile: packFile,
44 | result: &Result{
45 | status: STATUS_INIT,
46 | },
47 | }
48 | if err := build.createScriptFile(scripts); err != nil {
49 | return build, err
50 | }
51 | build.initBuildTask()
52 |
53 | return build, nil
54 | }
55 |
56 | func (b *Build) createScriptFile(scripts string) error {
57 | b.scriptFile = gostring.JoinStrings(b.tmp, "/", gostring.StrRandom(24), ".sh")
58 | s := gostring.JoinStrings(
59 | "#!/bin/bash\n\n",
60 | "#--------- build scripts env ---------\n",
61 | fmt.Sprintf("env_workspace=%s\n", b.local),
62 | fmt.Sprintf("env_pack_file=%s\n", b.packFile),
63 | scripts,
64 | )
65 | if err := gofile.CreateFile(b.scriptFile, []byte(s), 0744); err != nil {
66 | return err
67 | }
68 | return nil
69 | }
70 |
71 | func (b *Build) initBuildTask() {
72 | cmds := b.repo.Fetch()
73 | cmds = append(cmds, []string{
74 | "echo \"Now is\" `date`",
75 | "echo \"Run user is\" `whoami`",
76 | fmt.Sprintf("rm -f %s", b.packFile),
77 | fmt.Sprintf("/bin/bash -c %s", b.scriptFile),
78 | fmt.Sprintf("rm -f %s", b.scriptFile),
79 | fmt.Sprintf("rm -fr %s", b.local),
80 | "echo \"Compile completed\" `date`",
81 | }...)
82 | b.task = command.NewTask(cmds, COMMAND_TIMEOUT)
83 | }
84 |
85 | func (b *Build) Run() {
86 | b.result.status = STATUS_ING
87 | b.result.stime = int(time.Now().Unix())
88 | b.task.Run()
89 | if err := b.task.GetError(); err != nil {
90 | b.result.status = STATUS_FAILED
91 | b.result.err = err
92 | } else {
93 | b.result.status = STATUS_DONE
94 | }
95 | b.result.etime = int(time.Now().Unix())
96 | }
97 |
98 | func (b *Build) Result() *Result {
99 | return b.result
100 | }
101 |
102 | func (b *Build) Output() []*command.TaskResult{
103 | return b.task.Result()
104 | }
105 |
106 | func (b *Build) PackFile() string {
107 | return b.packFile
108 | }
109 |
110 | func (b *Build) PackRealFile() string {
111 | if gopath.Exists(b.packFile) {
112 | return b.packFile
113 | }
114 | return ""
115 | }
116 |
117 | func (b *Build) Terminate() {
118 | if b.task != nil {
119 | b.task.Terminate()
120 | }
121 | }
122 |
123 | type Result struct {
124 | err error
125 | status int
126 | stime int
127 | etime int
128 | }
129 |
130 | func (r *Result) During() int {
131 | return r.etime - r.stime
132 | }
133 |
134 | func (r *Result) Status() int {
135 | return r.status
136 | }
137 |
138 | func (r *Result) GetError() error {
139 | return r.err
140 | }
--------------------------------------------------------------------------------
/build/build_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package build
6 |
7 | import (
8 | "testing"
9 | "time"
10 | "fmt"
11 |
12 | "github.com/dreamans/syncd/util/command"
13 | )
14 |
15 | func TestBuild(t *testing.T) {
16 | local := "/tmp/laravel"
17 | tmp := "/tmp"
18 | packFile := "/tmp/laravel.tar.gz"
19 | repo := NewRepo("git@gitee.com:mirrors/laravel.git", local)
20 | scripts := `
21 | cd ${env_workspace}
22 | tar --exclude=.git --exclude=.gitignore -zcvf ${env_pack_file} *
23 | `
24 | build, err := NewBuild(repo, local, tmp, packFile, scripts)
25 | if err != nil {
26 | t.Errorf("create build task failed: %s", err.Error())
27 | }
28 | NewTask(1, build, func(id int, result *Result, taskResult []*command.TaskResult) {
29 | fmt.Println(id)
30 | fmt.Println(result)
31 | for _, t := range taskResult {
32 | fmt.Println(t)
33 | }
34 | })
35 |
36 | time.Sleep(2 * time.Second)
37 |
38 | result, taskResult, err := StatusTask(1)
39 |
40 | fmt.Println(result)
41 | for _, t := range taskResult {
42 | fmt.Println(t)
43 | }
44 |
45 | time.Sleep(10 * time.Second)
46 |
47 | }
--------------------------------------------------------------------------------
/build/repo.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package build
6 |
7 | import (
8 | "fmt"
9 | )
10 |
11 | type Repo struct {
12 | url string
13 | branch string
14 | commit string
15 | local string
16 | }
17 |
18 | func NewRepo(url, local string) *Repo {
19 | repo := &Repo{
20 | url: url,
21 | local: local,
22 | }
23 | return repo
24 | }
25 |
26 | func (r *Repo) SetBranch(branch string) {
27 | r.branch = branch
28 | }
29 |
30 | func (r *Repo) SetCommit(version string) {
31 | r.commit = version
32 | }
33 |
34 | func (r *Repo) Fetch() []string {
35 | cmds := []string{
36 | fmt.Sprintf("rm -fr %s", r.local),
37 | fmt.Sprintf("/usr/bin/env git clone -q %s %s", r.url, r.local),
38 | }
39 | if r.branch != "" {
40 | cmds = append(cmds, fmt.Sprintf("cd %s && /usr/bin/env git checkout -q %s", r.local, r.branch))
41 | }
42 | if r.commit != "" {
43 | cmds = append(cmds, fmt.Sprintf("cd %s && /usr/bin/env git reset -q --hard %s", r.local, r.commit))
44 | }
45 | return cmds
46 | }
--------------------------------------------------------------------------------
/build/task.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package build
6 |
7 | import (
8 | "sync"
9 | "errors"
10 | "fmt"
11 |
12 | "github.com/dreamans/syncd/util/command"
13 | )
14 |
15 | type buildTask struct {
16 | builds map[int]*Build
17 | mu sync.Mutex
18 | }
19 |
20 | type CallbackFn func(int, string, *Result, []*command.TaskResult)
21 |
22 | var task = &buildTask{
23 | builds: make(map[int]*Build),
24 | }
25 |
26 | func NewTask(id int, build *Build, fn CallbackFn) error {
27 | if exists := task.exists(id); exists {
28 | return errors.New(fmt.Sprintf("build task [id: %d] have exists", id))
29 | }
30 | task.append(id, build)
31 | go func() {
32 | build.Run()
33 | task.remove(id)
34 | if fn != nil {
35 | fn(id, build.PackRealFile(), build.Result(), build.Output())
36 | }
37 | }()
38 | return nil
39 | }
40 |
41 | func StopTask(id int) {
42 | task.stop(id)
43 | }
44 |
45 | func StatusTask(id int) (*Result, []*command.TaskResult, error) {
46 | build, exists := task.get(id)
47 | if !exists {
48 | return nil, nil, errors.New(fmt.Sprintf("build task [id: %d] not exists", id))
49 | }
50 | return build.Result(), build.Output(), nil
51 | }
52 |
53 | func (t *buildTask) exists(id int) bool {
54 | t.mu.Lock()
55 | defer t.mu.Unlock()
56 | _, exists := t.builds[id]
57 | return exists
58 | }
59 |
60 | func (t *buildTask) append(id int, build *Build) {
61 | t.mu.Lock()
62 | defer t.mu.Unlock()
63 | t.builds[id] = build
64 | }
65 |
66 | func (t *buildTask) remove(id int) {
67 | t.mu.Lock()
68 | defer t.mu.Unlock()
69 | delete(t.builds, id)
70 | }
71 |
72 | func (t *buildTask) get(id int) (*Build, bool) {
73 | t.mu.Lock()
74 | defer t.mu.Unlock()
75 | build, exists := t.builds[id]
76 | return build, exists
77 | }
78 |
79 | func (t *buildTask) stop(id int) {
80 | t.mu.Lock()
81 | defer t.mu.Unlock()
82 | build, exists := t.builds[id]
83 | if exists {
84 | build.Terminate()
85 | }
86 | }
--------------------------------------------------------------------------------
/config.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package syncd
6 |
7 | type (
8 | Config struct {
9 | Serve *ServeConfig
10 | Db *DbConfig
11 | Log *LogConfig
12 | Syncd *SyncdConfig
13 | Mail *MailConfig
14 | }
15 |
16 | SyncdConfig struct {
17 | LocalSpace string
18 | RemoteSpace string
19 | Cipher string
20 | AppHost string
21 | }
22 |
23 | LogConfig struct {
24 | Path string
25 | }
26 |
27 | MailConfig struct {
28 | Enable int
29 | Smtp string
30 | Port int
31 | User string
32 | Pass string
33 | }
34 |
35 | ServeConfig struct {
36 | Addr string
37 | FeServeEnable int
38 | ReadTimeout int
39 | WriteTimeout int
40 | IdleTimeout int
41 | }
42 |
43 | DbConfig struct {
44 | Unix string
45 | Host string
46 | Port int
47 | Charset string
48 | User string
49 | Pass string
50 | DbName string
51 | TablePrefix string
52 | MaxIdleConns int
53 | MaxOpenConns int
54 | ConnMaxLifeTime int
55 | }
56 | )
57 |
--------------------------------------------------------------------------------
/db.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package syncd
6 |
7 | import (
8 | "fmt"
9 | "time"
10 | "errors"
11 |
12 | "github.com/jinzhu/gorm"
13 | _ "github.com/go-sql-driver/mysql"
14 | )
15 |
16 | type DB struct {
17 | DbHandler *gorm.DB
18 | cfg *DbConfig
19 | }
20 |
21 | func NewDatabase(cfg *DbConfig) *DB {
22 | return &DB{
23 | cfg: cfg,
24 | }
25 | }
26 |
27 | func (db *DB) Open() error {
28 | c, err := gorm.Open("mysql", db.parseConnConfig())
29 | if err != nil {
30 | return errors.New(fmt.Sprintf("mysql connect failed, %s", err.Error()))
31 | }
32 |
33 | c.LogMode(false)
34 | c.DB().SetMaxIdleConns(db.cfg.MaxIdleConns)
35 | c.DB().SetMaxOpenConns(db.cfg.MaxOpenConns)
36 | c.DB().SetConnMaxLifetime(time.Second * time.Duration(db.cfg.ConnMaxLifeTime))
37 |
38 | db.DbHandler = c
39 | return nil
40 | }
41 |
42 | func (db *DB) Close() {
43 | db.DbHandler.Close()
44 | }
45 |
46 | func (db *DB) parseConnConfig() string {
47 | var connHost string
48 | if db.cfg.Unix != "" {
49 | connHost = fmt.Sprintf("unix(%s)", db.cfg.Unix)
50 | } else {
51 | connHost = fmt.Sprintf("tcp(%s:%d)", db.cfg.Host, db.cfg.Port)
52 | }
53 | s := fmt.Sprintf("%s:%s@%s/%s?charset=%s&parseTime=True&loc=Local", db.cfg.User, db.cfg.Pass, connHost, db.cfg.DbName, db.cfg.Charset)
54 | return s
55 | }
56 |
--------------------------------------------------------------------------------
/deploy/deploy.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package deploy
6 |
7 | import (
8 | "sync"
9 | )
10 |
11 | const (
12 | STATUS_INIT = 0
13 | STATUS_ING = 1
14 | STATUS_DONE = 2
15 | STATUS_FAILED = 3
16 | )
17 |
18 | const (
19 | DEPLOY_PARALLEL = 1
20 | DEPLOY_SERIAL = 2
21 | )
22 |
23 | type Deploy struct {
24 | ID int
25 | User string
26 | PreCmd string
27 | PostCmd string
28 | DeployPath string
29 | DeployTmpPath string
30 | PackFile string
31 | srvs []*Server
32 | status int
33 | wg sync.WaitGroup
34 | }
35 |
36 | type DeployResult struct {
37 | ID int
38 | Status int
39 | ServerRest []*ServerResult
40 | }
41 |
42 | func (d *Deploy) AddServer(id int, addr string, port int) {
43 | srv := &Server{
44 | ID: id,
45 | Addr: addr,
46 | User: d.User,
47 | Port: port,
48 | PreCmd: d.PreCmd,
49 | PostCmd: d.PostCmd,
50 | PackFile: d.PackFile,
51 | DeployTmpPath: d.DeployTmpPath,
52 | DeployPath: d.DeployPath,
53 | }
54 | NewServer(srv)
55 | d.srvs = append(d.srvs, srv)
56 | }
57 |
58 | func (d *Deploy) Parallel() {
59 | if d.status == STATUS_FAILED {
60 | return
61 | }
62 | d.status = STATUS_ING
63 | status := STATUS_DONE
64 | for _, srv := range d.srvs {
65 | d.wg.Add(1)
66 | go func() {
67 | if d.status == STATUS_ING {
68 | srv.Deploy()
69 | if srv.Result().Status == STATUS_FAILED {
70 | status = STATUS_FAILED
71 | }
72 | }
73 | defer d.wg.Done()
74 | }()
75 | }
76 | d.wg.Wait()
77 | d.status = status
78 | }
79 |
80 | func (d *Deploy) Serial() {
81 | if d.status == STATUS_FAILED {
82 | return
83 | }
84 | d.status = STATUS_ING
85 | status := STATUS_DONE
86 | for _, srv := range d.srvs {
87 | if d.status == STATUS_ING {
88 | srv.Deploy()
89 | if srv.Result().Status == STATUS_FAILED {
90 | status = STATUS_FAILED
91 | }
92 | }
93 | }
94 | d.status = status
95 | }
96 |
97 | func (d *Deploy) Result() ([]*ServerResult, int) {
98 | var rest []*ServerResult
99 | for _, srv := range d.srvs {
100 | rest = append(rest, srv.Result())
101 | }
102 | return rest, d.status
103 | }
104 |
105 | func (d *Deploy) Terminate() {
106 | d.status = STATUS_FAILED
107 | for _, srv := range d.srvs {
108 | srv.Terminate()
109 | }
110 | }
--------------------------------------------------------------------------------
/deploy/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package deploy
6 |
7 | import (
8 | "fmt"
9 | "path"
10 |
11 | "github.com/dreamans/syncd/util/command"
12 | )
13 |
14 | const (
15 | COMMAND_TIMEOUT = 3600
16 | )
17 |
18 | type Server struct {
19 | ID int
20 | Addr string
21 | User string
22 | Port int
23 | PreCmd string
24 | PostCmd string
25 | Key string
26 | PackFile string
27 | DeployTmpPath string
28 | DeployPath string
29 | task *command.Task
30 | result *ServerResult
31 | }
32 |
33 | type ServerResult struct {
34 | ID int
35 | TaskResult []*command.TaskResult
36 | Status int
37 | Error error
38 | }
39 |
40 | func NewServer(srv *Server) {
41 | srv.result = &ServerResult{
42 | ID: srv.ID,
43 | Status: STATUS_INIT,
44 | }
45 | srv.task = command.NewTask(
46 | srv.deployCmd(),
47 | COMMAND_TIMEOUT,
48 | )
49 | }
50 |
51 | func (srv *Server) Deploy() {
52 | srv.result.Status = STATUS_ING
53 | srv.task.Run()
54 | if err := srv.task.GetError(); err != nil {
55 | srv.result.Error = err
56 | srv.result.Status = STATUS_FAILED
57 | } else {
58 | srv.result.Status = STATUS_DONE
59 | }
60 | }
61 |
62 | func (srv *Server) Terminate() {
63 | if srv.result.Status == STATUS_ING {
64 | srv.task.Terminate()
65 | }
66 | }
67 |
68 | func (srv *Server) Result() *ServerResult {
69 | srv.result.TaskResult = srv.task.Result()
70 | return srv.result
71 | }
72 |
73 | func (srv *Server) deployCmd() []string {
74 | var (
75 | useCustomKey, useSshPort, useScpPort string
76 | )
77 | if srv.Key != "" {
78 | useCustomKey = fmt.Sprintf("-i %s", srv.Key)
79 | }
80 | if srv.Port != 0 {
81 | useSshPort = fmt.Sprintf("-p %d", srv.Port)
82 | useScpPort = fmt.Sprintf(" -P %d", srv.Port)
83 | }
84 | var cmds []string
85 | if srv.PackFile == "" {
86 | cmds = append(cmds, "echo 'packfile empty' && exit 1")
87 | }
88 |
89 | cmds = append(cmds, []string{
90 | fmt.Sprintf(
91 | "/usr/bin/env ssh -o StrictHostKeyChecking=no %s %s %s@%s 'mkdir -p %s; mkdir -p %s'",
92 | useCustomKey,
93 | useSshPort,
94 | srv.User,
95 | srv.Addr,
96 | srv.DeployTmpPath,
97 | srv.DeployPath,
98 | ),
99 | fmt.Sprintf(
100 | "/usr/bin/env scp -o StrictHostKeyChecking=no -q %s %s %s %s@%s:%s/",
101 | useCustomKey,
102 | useScpPort,
103 | srv.PackFile,
104 | srv.User,
105 | srv.Addr,
106 | srv.DeployTmpPath,
107 | ),
108 | }...)
109 | if srv.PreCmd != "" {
110 | cmds = append(
111 | cmds,
112 | fmt.Sprintf(
113 | "/usr/bin/env ssh -o StrictHostKeyChecking=no %s %s %s@%s '%s'",
114 | useCustomKey,
115 | useSshPort,
116 | srv.User,
117 | srv.Addr,
118 | srv.PreCmd,
119 | ),
120 | )
121 | }
122 | packFileName := path.Base(srv.PackFile)
123 | cmds = append(
124 | cmds,
125 | fmt.Sprintf(
126 | "/usr/bin/env ssh -o StrictHostKeyChecking=no %s %s %s@%s 'cd %s; tar -zxf %s -C %s; rm -f %s'",
127 | useCustomKey,
128 | useSshPort,
129 | srv.User,
130 | srv.Addr,
131 | srv.DeployTmpPath,
132 | packFileName,
133 | srv.DeployPath,
134 | packFileName,
135 | ),
136 | )
137 | if srv.PostCmd != "" {
138 | cmds = append(
139 | cmds,
140 | fmt.Sprintf("/usr/bin/env ssh -o StrictHostKeyChecking=no %s %s %s@%s '%s'",
141 | useCustomKey,
142 | useSshPort,
143 | srv.User,
144 | srv.Addr,
145 | srv.PostCmd,
146 | ),
147 | )
148 | }
149 | return cmds
150 | }
--------------------------------------------------------------------------------
/deploy/task.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package deploy
6 |
7 | import (
8 | "sync"
9 | "errors"
10 | "fmt"
11 | )
12 |
13 | type deployTask struct {
14 | deploys map[int][]*Deploy
15 | mu sync.Mutex
16 | }
17 |
18 | type CallbackFn func(int, int, int, []*ServerResult)
19 |
20 | type TaskCallbackFn func(int, int)
21 |
22 | var task = &deployTask{
23 | deploys: map[int][]*Deploy{},
24 | }
25 |
26 | func NewTask(id, mode int, deploys []*Deploy, startFn, finishFn CallbackFn, taskFn TaskCallbackFn) error {
27 | if exists := task.exists(id); exists {
28 | return errors.New(fmt.Sprintf("deploy task [id: %d] have exists", id))
29 | }
30 | task.append(id, deploys)
31 | go func() {
32 | taskStatus := STATUS_DONE
33 | for _, deploy := range deploys {
34 | if startFn != nil {
35 | rest, status := deploy.Result()
36 | startFn(id, deploy.ID, status, rest)
37 | }
38 | switch mode {
39 | case DEPLOY_PARALLEL:
40 | deploy.Parallel()
41 | default:
42 | deploy.Serial()
43 | }
44 | resultList, status := deploy.Result()
45 | if finishFn != nil {
46 | finishFn(id, deploy.ID, status, resultList)
47 | }
48 | if status == STATUS_FAILED {
49 | taskStatus = STATUS_FAILED
50 | }
51 | }
52 | task.remove(id)
53 | if taskFn != nil {
54 | taskFn(id, taskStatus)
55 | }
56 | }()
57 | return nil
58 | }
59 |
60 | func StopTask(id int) {
61 | task.stop(id)
62 | }
63 |
64 | func ExistsTask(id int) bool {
65 | return task.exists(id)
66 | }
67 |
68 | func StatusTask(id int) []*DeployResult {
69 | deploys, exists := task.get(id)
70 | if !exists {
71 | return nil
72 | }
73 | rests := []*DeployResult{}
74 | for _, deploy := range deploys {
75 | rest, s := deploy.Result()
76 | rests = append(rests, &DeployResult{
77 | ID: deploy.ID,
78 | Status: s,
79 | ServerRest: rest,
80 | })
81 | }
82 | return rests
83 | }
84 |
85 | func (t *deployTask) exists(id int) bool {
86 | t.mu.Lock()
87 | defer t.mu.Unlock()
88 | _, exists := t.deploys[id]
89 | return exists
90 | }
91 |
92 | func (t *deployTask) append(id int, deploys []*Deploy) {
93 | t.mu.Lock()
94 | defer t.mu.Unlock()
95 | t.deploys[id] = deploys
96 | }
97 |
98 | func (t *deployTask) remove(id int) {
99 | t.mu.Lock()
100 | defer t.mu.Unlock()
101 | delete(t.deploys, id)
102 | }
103 |
104 | func (t *deployTask) get(id int) ([]*Deploy, bool) {
105 | t.mu.Lock()
106 | defer t.mu.Unlock()
107 | deploys, exists := t.deploys[id]
108 | return deploys, exists
109 | }
110 |
111 | func (t *deployTask) stop(id int) {
112 | t.mu.Lock()
113 | defer t.mu.Unlock()
114 | deploys, exists := t.deploys[id]
115 | if exists {
116 | for _, deploy := range deploys {
117 | deploy.Terminate()
118 | }
119 | }
120 | }
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/2.0/.nojekyll:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/assets/css/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: rgba(0,0,0,.87);
3 | }
4 | a {
5 | color: #409EFF;
6 | text-decoration: none;
7 | }
8 | a:hover {
9 | color: #66b1ff;
10 | }
11 | .bg-gray {
12 | background-color: #f4f4f4;
13 | }
14 | .bg-dark {
15 | background-color: #171f26;
16 | }
17 | .text-center {
18 | text-align: center;
19 | }
20 | .wrap {
21 | max-width: 1200px;
22 | margin: 0 auto;
23 | }
24 | .icon-large {
25 | color: #666;
26 | font-size: 36px;
27 | }
28 | .iconfont.left {
29 | margin-right: 6px;
30 | }
31 | .header-container {
32 | position: relative;
33 | padding: 20px 0;
34 | color: #fff;
35 | }
36 | .header-container .nav {
37 | color: #fff;
38 | display:flex;
39 | }
40 | .header-container .nav .logo img {
41 | height: 30px;
42 | }
43 | .header-container .nav .nav-list {
44 | display: flex;
45 | align-items: center;
46 | margin-left: 30px;
47 | }
48 | .header-container .nav-link {
49 | color: #fff;
50 | text-decoration: none;
51 | opacity: .7;
52 | padding: 0 15px;
53 | }
54 | .header-container .nav-link:hover {
55 | opacity: 1;
56 | color: #409EFF;
57 | }
58 | .header-container .nav-social {
59 | padding: 0 5px;
60 | }
61 | .header-container .banner {
62 | height: 450px;
63 | opacity: .7;
64 | overflow: hidden;
65 | }
66 | .header-container .banner .h {
67 | font-weight: 300;
68 | }
69 | .header-container .banner .tit {
70 | font-size: 50px;
71 | padding-top: 100px;
72 | }
73 | .header-container .banner .subtit {
74 | font-size: 20px;
75 | }
76 | .header-container .banner .start {
77 | padding: 60px 0;
78 | text-align: center;
79 | }
80 | .header-container .banner .start-link {
81 | background: #283542;
82 | padding: 15px 20px;
83 | margin: 0 10px;
84 | width: 90px;
85 | opacity: 1;
86 | }
87 | .header-container .banner .start-link.main{
88 | background: #1989fa;
89 | }
90 | .header-container .banner .start-link:hover {
91 | color: #fff;
92 | background: #409EFF;
93 | }
94 | .section-container {
95 | padding: 36px 0;
96 | }
97 | .features-list {
98 | padding: 0;
99 | overflow: hidden;
100 | display: flex;
101 | justify-content: space-between;
102 | }
103 | .features-list:not(:first-child) {
104 | margin-top: 48px;
105 | }
106 | .features-list li {
107 | list-style: none;
108 | display: flex;
109 | flex-direction: column;
110 | align-items: center;
111 | width: 280px;
112 | }
113 | .features-list .desc {
114 | margin-top: 24px;
115 | }
116 | .features-tit {
117 | padding: 15px 0;
118 | font-size: 30px;
119 | position: relative;
120 | }
121 | .features-tit:after {
122 | content: '';
123 | width: 50px;
124 | height: 2px;
125 | background-color: #333;
126 | position: absolute;
127 | bottom: 0;
128 | left: 50%;
129 | transform: translateX(-50%);
130 | }
131 | .quick-shell {
132 | padding: 12px;
133 | border-radius: 4px;
134 | background: #282a36;
135 | color: #fff;
136 | width: 720px;
137 | margin: 32px auto;
138 | }
139 | .quick-shell pre {
140 | margin: 0;
141 | padding: 0;
142 | line-height: 1.6;
143 | }
144 | .quick-internet {
145 | color: #fff;
146 | opacity: .8;
147 | }
148 | .license-text {
149 | width: 500px;
150 | line-height: 1.8;
151 | margin: 0 auto;
152 | }
153 | .license-text .logo-large {
154 | margin: 80px auto;
155 | }
156 | .footer-copyright p{
157 | color: #a3a4a9;
158 | }
159 | .footer-copyright p {
160 | margin: 0;
161 | padding: 3px 0;
162 | }
163 | .footer-copyright a {
164 | color: #a3a4a9;
165 | }
166 | .footer-copyright a:hover {
167 | color: #fff;
168 | }
--------------------------------------------------------------------------------
/docs/assets/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/docs/assets/img/favicon.ico
--------------------------------------------------------------------------------
/docs/assets/img/gitee-logo-black.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/assets/img/wechat-donate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/docs/assets/img/wechat-donate.png
--------------------------------------------------------------------------------
/docs/docs/_404.md:
--------------------------------------------------------------------------------
1 | Coming Soon...
--------------------------------------------------------------------------------
/docs/docs/_sidebar.md:
--------------------------------------------------------------------------------
1 | * 快速开始
2 | * [概述](/)
3 | * [运行原理](architecture.md)
4 | * [编译安装](install.md)
5 | * [服务配置](setting.md)
6 | * [获取帮助](help.md)
7 | * [安装问题列表](troubleinstall.md)
8 | * 使用方法
9 | * [项目配置](project.md)
10 | * [集群配置](server.md)
11 | * [构建配置](build.md)
12 | * [使用问题列表](troubleuse.md)
13 | * 其他
14 | * [捐赠](donate.md)
--------------------------------------------------------------------------------
/docs/docs/architecture.md:
--------------------------------------------------------------------------------
1 | # 运行原理
2 |
3 | ## 原理
4 |
5 | Syncd通过 `git-ssh` 方式创建仓库本地副本,通过运行项目构建脚本来生成可发布的代码包,使用 `scp` 串行/并行地向生产服务器分发并解包部署。
6 |
7 | Syncd与生产服务器通过ssh交互,因此Syncd运行用户的 ssh-key 必须加入到目标主机的用户ssh-key信任列表中。
8 |
9 |
10 |
11 | ## Git
12 |
13 | Syncd目前仅支持Git仓库部署,并且通过 `git-ssh` 方式从仓库中拉取指定tag(分支)代码。
14 |
15 | ## 构建
16 |
17 | 部署过程中,在拉取git仓库到本地后会运行项目的自定义构建脚本,编译成可上线的软件包。
18 |
19 | 在这一环节中可运行:
20 | - 单元测试,如 `go test`, `php phpunit`
21 | - 下载依赖,如 `go: glide install`, `php: composer install`
22 | - 编译软件包,如 `js: npm build`, `go: go build xx.go`, `java: mvn ...`
23 |
24 | ## 分发
25 |
26 | 通过 `scp` 命令分发软件包到各机房生产服务器的临时目录, 远程执行 `pre-deploy` 配置的命令, 执行完毕后解压缩软件包到目标目录,然后执行 `post-deploy` 命令。
27 |
28 | 分发上线过程是集群之间串行执行,集群内部可串行可并行。
29 |
30 | ## SSH信任
31 |
32 | 生产服务器与部署服务器之间通过ssh-key建立信任,配置方法请参考 [秘钥配置](server.md?id=秘钥配置)
33 |
34 | [filename](include/footer.md ':include')
--------------------------------------------------------------------------------
/docs/docs/assets/css/app.css:
--------------------------------------------------------------------------------
1 | .app-logo {
2 | margin: 50px auto 0;
3 | width: 300px;
4 | display: block;
5 | }
6 | .app-donate {
7 | width: 220px;
8 | border: 1px solid #dedede;
9 | padding: 8px;
10 | border-radius: 5px;
11 | }
12 | .app-donate .tit {
13 | text-align: center;
14 | display: block;
15 | padding: 10px 0;
16 | }
17 | .app-image {
18 | border: 1px solid #dedede;
19 | border-radius: 5px;
20 | }
21 | .app-img-eg {
22 | width: 800px;
23 | }
24 | .app-logo-img {
25 | width: 60%;
26 | }
--------------------------------------------------------------------------------
/docs/docs/assets/img/syncd-member-add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/docs/docs/assets/img/syncd-member-add.png
--------------------------------------------------------------------------------
/docs/docs/assets/img/syncd-principle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/docs/docs/assets/img/syncd-principle.png
--------------------------------------------------------------------------------
/docs/docs/assets/img/syncd-project-add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/docs/docs/assets/img/syncd-project-add.png
--------------------------------------------------------------------------------
/docs/docs/assets/img/syncd-server-add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/docs/docs/assets/img/syncd-server-add.png
--------------------------------------------------------------------------------
/docs/docs/assets/img/syncd-server-group-add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/docs/docs/assets/img/syncd-server-group-add.png
--------------------------------------------------------------------------------
/docs/docs/assets/img/syncd-space-add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/docs/docs/assets/img/syncd-space-add.png
--------------------------------------------------------------------------------
/docs/docs/build.md:
--------------------------------------------------------------------------------
1 | # 构建配置
2 |
3 | ## 配置说明
4 |
5 | 通过项目列表中的 `构建设置` 来编辑构建脚本。脚本需在上线单中手动触发,系统会使用 `/bin/bash -c {脚本文件}` 执行。
6 |
7 | ## 构建脚本支持的变量
8 |
9 | - **${env_workspace}**
10 |
11 | 代码仓库本地副本目录
12 |
13 | - **${env_pack_file}**
14 |
15 | 打包文件绝对地址,构建完成后将需要部署到线上的代码打包到此文件中,必须使用 `tar -zcf` 命令进行打包。
16 |
17 | 部署模块会将此压缩包分发到目标主机并解压缩到指定目录,请按照要求打包,否则会部署失败。
18 |
19 | ## 简单构建示例
20 |
21 | ```shell
22 | cd ${env_workspace}
23 | tar --exclude='.git' -zcvf ${env_pack_file} *
24 | ```
25 |
26 | ## Laravel构建示例
27 |
28 | ```shell
29 | cd ${env_workspace}
30 | composer install
31 | tar --exclude='.git' -zcvf ${env_pack_file} *
32 | ```
33 |
34 | ## 前端项目构建示例
35 |
36 | ```shell
37 | cd ${env_workspace}
38 | yarn
39 | yarn build
40 | cd ./dist
41 | tar -zcvf ${env_pack_file} *
42 | ```
43 |
44 | ## Java项目构建示例
45 |
46 | 待补充
47 |
48 | ## Syncd构建示例
49 |
50 | ```shell
51 | cd ${env_workspace}
52 | make
53 | cd ./output
54 | tar -zcvf ${env_pack_file} *
55 | ```
56 |
57 | [filename](include/footer.md ':include')
--------------------------------------------------------------------------------
/docs/docs/donate.md:
--------------------------------------------------------------------------------
1 | # 捐赠我们
2 |
3 | Syncd 一直在致力于为企业和个人提供简洁高效的持续集成工具,您的帮助是对我们最大的支持和动力!
4 |
5 | ### 扫码捐赠
6 |
7 |
8 |

9 |
微信扫码捐赠
10 |
11 |
12 | ### 捐赠列表
13 |
14 | | Name | Date | Amount | Via | Note |
15 | | ------------ | ---------- | -------- | ------ | -------------------- |
16 | | *🍉 | 2020-03-28 | ¥50.00 | Wechat | |
17 | | W*l | 2020-03-10 | ¥666.00 | Wechat | |
18 | | lizhibo | 2019-04-11 | ¥68.88 | Wechat | |
19 | | k8svip | 2019-04-01 | ¥100.00 | Wechat | |
20 |
21 | [filename](include/footer.md ':include')
22 |
--------------------------------------------------------------------------------
/docs/docs/help.md:
--------------------------------------------------------------------------------
1 | # 获取帮助
2 |
3 | Syncd使用交流QQ群①: 725302833
4 |
5 | [filename](include/footer.md ':include')
--------------------------------------------------------------------------------
/docs/docs/home.md:
--------------------------------------------------------------------------------
1 | # Syncd - 自动化部署工具
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 欢迎使用 Syncd,本文档将帮助你快速上手。如果您在使用过程中遇到问题,请查看 [问题解答](troubleshooting.md) 中的解答,或者在 [GitHub Issue](https://github.com/dreamans/syncd/issues) 上提问。
12 |
13 | ## 什么是 Syncd?
14 |
15 | Syncd是一款开源的代码部署工具,它具有简单、高效、易用等特点,可以提高团队的工作效率。
16 |
17 | 码云GVP项目
18 |
19 | [](https://gitee.com/dreamans/syncd)
20 |
21 | ## 特性
22 |
23 | - Go语言开发,编译简单、运行高效
24 | - Web界面访问,交互友好
25 | - 权限模型灵活自由
26 | - 支持自定义构建
27 | - 支持Git仓库
28 | - 支持分支、Tag上线
29 | - 部署Hook支持,可扩展性强
30 | - 完善的上线工作流
31 | - 邮件通知机制
32 |
33 | ## 项目地址
34 |
35 | Github: https://github.com/dreamans/syncd
36 |
37 | Gitee: https://gitee.com/dreamans/syncd
38 |
39 | ## 捐赠
40 |
41 | Syncd的发展离不开您的支持,[前往捐赠](donate.md)
42 |
43 | ## LICENSE
44 |
45 | 本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 LICENSE 文件中。
46 |
47 | [filename](include/footer.md ':include')
--------------------------------------------------------------------------------
/docs/docs/include/footer.md:
--------------------------------------------------------------------------------
1 | ----
2 | 文档最后更新时间: {docsify-updated}
--------------------------------------------------------------------------------
/docs/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 文档 | Syncd
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
22 |
35 |
36 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/docs/docs/install.md:
--------------------------------------------------------------------------------
1 | # 安装
2 |
3 | 安装 Syncd 只需几分钟时间,若您在安装过程中遇到问题或无法找到解决方式,请[提交Issue](https://github.com/dreamans/syncd/issues),我们会尽力解决您的问题。
4 |
5 | ## 环境需求
6 |
7 | **操作系统**
8 |
9 | Linux / macOS + Bash. 需要注意的是Syncd不支持Win系统。
10 |
11 | **Go 编译环境**
12 |
13 | Syncd依赖 `Go1.11+` 编译环境,可前往[官方网站](https://golang.org/dl/) 或 [国内镜像](https://golang.google.cn/dl/) 下载安装。
14 |
15 | **MySQL**
16 |
17 | MySQL 5.6+
18 |
19 | **Git**
20 |
21 | 升级操作系统Git到最新版本。
22 |
23 | ## 编译Syncd
24 |
25 | 执行以下命令
26 | ```shell
27 | $ curl https://syncd.cc/install.sh | bash
28 | ```
29 |
30 | 若编译程序未报错并且在当前目录中生成 `syncd-deploy` 子目录,则表示安装成功。
31 |
32 | syncd-deploy目录结构
33 |
34 | ```shell
35 | syncd-deploy // syncd-deploy可修改为任意其他目录名称
36 | ├── bin // bin目录存放Syncd的可执行文件
37 | │ └── syncd
38 | ├── etc // bin/syncd 程序运行时若不指定配置文件,则会在etc目录中查找syncd.ini作为默认配置
39 | │ └── syncd.ini
40 | ├── log
41 | ├── public // 静态资源目录
42 | ├── css
43 | ├── favicon.ico
44 | ├── fonts
45 | ├── img
46 | ├── index.html
47 | └── js
48 | └── resource // 资源目录
49 | └── sql
50 | ```
51 |
52 | > 生成的 syncd-deploy 目录可拷贝或移动到你想要的地方,但不要试图将此目录拷贝到其他服务器上运行,会造成不可预料的结果。
53 |
54 | ## 导入数据库
55 |
56 | Syncd依赖的MySQL数据表结构存在于 `syncd-deploy/resource/sql/syncd_{版本号}.sql` 中
57 |
58 | ```
59 | $ cd ./syncd-deploy
60 | $ mysql --default-character-set=utf8mb4 < ./resource/sql/syncd_{版本号}.sql # 导入MySQL表结构
61 | ```
62 |
63 | ## 配置文件
64 |
65 | 配置文件位置: `syncd-deploy/etc/syncd.ini`
66 |
67 | 修改数据库连接信息(查看[配置项](setting.md)详细文档)
68 |
69 | ```ini
70 | [database]
71 | host = 127.0.0.1
72 | port = 3306
73 | user = syncd
74 | password = syncd
75 | dbname = syncd
76 | ```
77 |
78 | ## 运行程序
79 |
80 | ```shell
81 | $ cd syncd-deploy
82 | $ ./bin/syncd
83 |
84 | _____ __ __ ____ _____ ____/ /
85 | / ___/ / / / / / __ \ / ___/ / __ /
86 | (__ ) / /_/ / / / / / / /__ / /_/ /
87 | /____/ \__, / /_/ /_/ \___/ \__,_/
88 | /____/
89 |
90 | Service: syncd
91 | Version: v2.0.0
92 | Config Loaded: ./etc/syncd.ini
93 | Log: stdout
94 | Mail Enable: 0
95 | HTTP Service: :8878
96 | Start Running...
97 | ```
98 |
99 | 打开浏览器,访问 `http://localhost:8878`
100 |
101 | 初始账号:
102 | ```
103 | 用户名: syncd
104 | 密码: 111111
105 | ```
106 | **!!!登录后尽快修改默认密码**
107 |
108 | [filename](include/footer.md ':include')
--------------------------------------------------------------------------------
/docs/docs/project.md:
--------------------------------------------------------------------------------
1 | # 项目配置
2 |
3 | ## 项目空间
4 |
5 | 项目空间是项目的基本组织单元,是进行项目和多用户隔离和访问控制的主要边界。
6 |
7 | 项目 -> 空间管理 -> 新增项目空间
8 |
9 |
10 |
11 | ## 项目管理
12 |
13 | 项目 -> 项目管理 -> [切换项目空间] -> 新增项目
14 |
15 |
16 |
17 | ## 成员管理
18 |
19 | 只有将用户添加到该项目空间,成为空间成员后才具有相应的权限。
20 |
21 | 项目 -> 成员管理 -> [切换项目空间] -> 添加新成员
22 |
23 | 在 `添加新成员` 输入框中输入待加入成员的关键词,选中用户后点击添加。
24 |
25 |
26 |
27 | [filename](include/footer.md ':include')
--------------------------------------------------------------------------------
/docs/docs/server.md:
--------------------------------------------------------------------------------
1 | # 集群配置
2 |
3 | ## 添加集群
4 |
5 | 在创建项目前,需要先添加好项目所需的服务器集群,新建集群步骤如下:
6 |
7 | 服务器 -> 集群管理 -> 新增集群
8 |
9 | 按实际情况输入集群名称,如图:
10 |
11 |
12 |
13 | ## 添加服务器
14 |
15 | 在新建的集群中添加服务器信息,步骤如下:
16 |
17 | 服务器 -> 服务器管理 -> 新增服务器
18 |
19 |
20 |
21 | ## 秘钥配置
22 |
23 | 由于部署服务器(Syncd服务所在的服务器)与生产服务器(代码部署目标机)之间通过ssh协议通信,所以需要将部署服务器的公钥 (一般在这里: `$HOME/.ssh/id_rsa.pub`)加入到生产机的信任列表中(一般在这里 `$HOME/.ssh/authorized_keys`)
24 |
25 | 可使用 `ssh-copy-id` 命令添加,或手动拷贝。
26 |
27 | 设置完成后不要忘记进行测试连通性 `ssh {生产机用户名}@{生产机地址}`
28 |
29 | > 注意:部署服务器的$HOME必须与运行Syncd的用户一致,否则ssh的免密登录将无效。
30 |
31 | [filename](include/footer.md ':include')
--------------------------------------------------------------------------------
/docs/docs/setting.md:
--------------------------------------------------------------------------------
1 | # 配置参考
2 |
3 | Syncd默认的配置文件位置为 `output/etc/syncd.ini`,也可以在运行时使用 `-c` 参数指定配置文件,如:
4 |
5 | ```
6 | $ bin/syncd -c [path/syncd.ini]
7 | ```
8 |
9 | ## 配置项说明
10 |
11 | ## syncd
12 |
13 | #### app_host
14 |
15 | - Type: `string`
16 | - Default: `http://localhost:8878`
17 |
18 | 项目访问域名, 结尾不要加 `/`,主要是在邮件中使用。
19 |
20 | #### local_space
21 |
22 | - Type: `string`
23 | - Default: `/tmp/syncd_data`
24 |
25 | 本地工作目录,Syncd所需的临时文件和编译打包归档文件都会在此目录中。
26 |
27 | #### remote_space
28 |
29 | - Type: `string`
30 | - Default: `~/.syncd`
31 |
32 | 目标主机临时工作目录,用来临时存放需要部署的打包文件。
33 |
34 | #### cipher_key
35 |
36 | - Type: `string`
37 | - Default: `pG1L62EM0cPIIOwusQsbcV8Cs6j/M1RxLoXIZylWUC4=`
38 |
39 | AES加密/解密使用的私钥,秘钥需要进行base64编码
40 |
41 | ## serve
42 |
43 | #### addr
44 |
45 | - Type: `string`
46 | - Default: `:8878`
47 |
48 | HTTP服务监听的端口,Syncd默认监听8878端口
49 |
50 | #### fe_serve_enable
51 |
52 | - Type: `int`
53 | - Default: `1`
54 |
55 | 是否开启前端资源服务,开启后Syncd前端资源将不再依赖nginx等web服务。
56 |
57 | 可选值:
58 | - 1 开启
59 | - 0 关闭
60 |
61 | #### read_timeout
62 |
63 | - Type: `int`
64 | - Default: `300`
65 |
66 | 读超时时间设置, 单位秒。
67 |
68 | #### write_timeout
69 |
70 | - Type: `int`
71 | - Default: `300`
72 |
73 | 写超时时间设置, 单位秒。
74 |
75 |
76 | #### idle_timeout
77 |
78 | - Type: `int`
79 | - Default: `300`
80 |
81 | 空闲连接超时设置, 单位秒。
82 |
83 | ## database
84 |
85 | #### unix
86 |
87 | - Type: `string`
88 | - Default: ` `
89 |
90 | 以 Unix Socket 方式连接MySQL。
91 |
92 | #### max_idle_conns
93 |
94 | - Type: `int`
95 | - Default: `100`
96 |
97 | 最大空闲连接数。
98 |
99 | #### max_open_conns
100 |
101 | - Type: `int`
102 | - Default: `200`
103 |
104 | MySQL最大连接数。
105 |
106 | #### conn_max_life_time
107 |
108 | - Type: `int`
109 | - Default: `500`
110 |
111 | 最长连接生命周期,单位秒。
112 |
113 | #### host
114 |
115 | - Type: `string`
116 | - Default: `127.0.0.1`
117 |
118 | MySQL连接主机名。
119 |
120 | #### port
121 |
122 | - Type: `int`
123 | - Default: `3306`
124 |
125 | MySQL连接端口。
126 |
127 | #### user
128 |
129 | - Type: `string`
130 | - Default: `syncd`
131 |
132 | MySQL连接用户名。
133 |
134 | #### password
135 |
136 | - Type: `string`
137 | - Default: `syncd`
138 |
139 | MySQL连接密码。
140 |
141 | #### dbname
142 |
143 | - Type: `string`
144 | - Default: `syncd`
145 |
146 | Syncd数据库名。
147 |
148 | ## log
149 |
150 | #### path
151 |
152 | - Type: `string`
153 | - Default: `stdout`
154 |
155 | 错误日志输出路径。
156 |
157 | 可选的值:
158 |
159 | - stdout 打印到标准输出
160 | - /path/file 输出到文件
161 |
162 | ## mail
163 |
164 | #### enable
165 |
166 | - Type: `int`
167 | - Default: `0`
168 |
169 | 是否开启邮件发送功能。
170 |
171 | #### smtp_host
172 |
173 | #### smtp_port
174 |
175 | #### smtp_user
176 |
177 | #### smtp_pass
178 |
179 | [filename](include/footer.md ':include')
--------------------------------------------------------------------------------
/docs/docs/troubleinstall.md:
--------------------------------------------------------------------------------
1 | # 安装问题列表
2 |
3 | [filename](include/footer.md ':include')
--------------------------------------------------------------------------------
/docs/docs/troubleuse.md:
--------------------------------------------------------------------------------
1 | # 使用问题列表
2 |
3 | #### 部署失败:[cmd] $ echo 'packfile empty' && exit 1 X
4 |
5 | 原因:构建脚本中未打包出tgz文件,检查是否使用 `tar -zcf` 命令进行打包。
6 |
7 | [filename](include/footer.md ':include')
--------------------------------------------------------------------------------
/docs/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | syncd_remote_repo=https://github.com/dreamans/syncd.git
4 | syncd_repo_path=$HOME/.syncd-repo
5 | syncd_install_path=$( cd `dirname $0`; pwd )/syncd-deploy
6 |
7 | checkCommand() {
8 | type $1 > /dev/null 2>&1
9 | if [ $? -ne 0 ]; then
10 | echo "error: $1 must be installed"
11 | echo "install exit"
12 | exit 1
13 | fi
14 | }
15 |
16 | checkCommand "go"
17 | checkCommand "git"
18 | checkCommand "make"
19 |
20 | if [ -d ${syncd_install_path} ];then
21 | syncd_install_path=${syncd_install_path}-$( date +%Y%m%d%H%M%S )
22 | fi
23 |
24 | rm -fr ${syncd_repo_path}
25 | git clone ${syncd_remote_repo} ${syncd_repo_path}
26 |
27 | cd ${syncd_repo_path}
28 | make
29 |
30 | rm -fr ${syncd_install_path}
31 | cp -r ${syncd_repo_path}/output ${syncd_install_path}
32 |
33 | rm -fr ${syncd_repo_path}
34 |
35 | cat << EOF
36 |
37 | Installing Syncd Path: ${syncd_install_path}
38 | Install complete.
39 |
40 | EOF
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/dreamans/syncd
2 |
3 | go 1.16
4 |
5 | require (
6 | cloud.google.com/go v0.36.0 // indirect
7 | github.com/Unknwon/goconfig v0.0.0-20181105214110-56bd8ab18619
8 | github.com/denisenkom/go-mssqldb v0.0.0-20190204142019-df6d76eb9289 // indirect
9 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
10 | github.com/gin-gonic/gin v1.4.0
11 | github.com/go-sql-driver/mysql v1.4.1
12 | github.com/gofrs/uuid v3.2.0+incompatible // indirect
13 | github.com/jinzhu/gorm v1.9.2
14 | github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
15 | github.com/jinzhu/now v1.0.0 // indirect
16 | github.com/lib/pq v1.0.0 // indirect
17 | github.com/mattn/go-sqlite3 v1.10.0 // indirect
18 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
19 | github.com/modern-go/reflect2 v1.0.1 // indirect
20 | github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa // indirect
21 | github.com/stretchr/testify v1.3.0 // indirect
22 | github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 // indirect
23 | google.golang.org/appengine v1.4.0 // indirect
24 | gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
25 | gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
26 | gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
27 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
28 | gopkg.in/yaml.v2 v2.2.2 // indirect
29 | )
30 |
--------------------------------------------------------------------------------
/model/deploy_apply.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import(
8 | "time"
9 | )
10 |
11 | type DeployApply struct {
12 | ID int `gorm:"primary_key"`
13 | SpaceId int `gorm:"type:int(11);not null;default:0"`
14 | ProjectId int `gorm:"type:int(11);not null;default:0"`
15 | Name string `gorm:"type:varchar(100);not null;default:''"`
16 | Description string `gorm:"type:varchar(500);not null;default:''"`
17 | BranchName string `gorm:"type:varchar(100);not null;default:''"`
18 | CommitVersion string `gorm:"type:varchar(50);not null;default:''"`
19 | AuditStatus int `gorm:"type:int(11);not null;default:0"`
20 | AuditRefusalReasion string `gorm:"type:varchar(500);not null;default:''"`
21 | Status int `gorm:"type:int(11);not null;default:0"`
22 | UserId int `gorm:"type:int(11);not null;default:0"`
23 | RollbackId int `gorm:"type:int(11);not null;default:0"`
24 | RollbackApplyId int `gorm:"type:int(11);not null;default:0"`
25 | IsRollbackApply int `gorm:"type:int(11);not null;default:0"`
26 | Ctime int `gorm:"type:int(11);not null;default:0"`
27 | }
28 |
29 | func (m *DeployApply) TableName() string {
30 | return "syd_deploy_apply"
31 | }
32 |
33 | func (m *DeployApply) Create() bool {
34 | m.Ctime = int(time.Now().Unix())
35 | return Create(m)
36 | }
37 |
38 | func (m *DeployApply) Update() bool {
39 | return UpdateByPk(m)
40 | }
41 |
42 | func (m *DeployApply) UpdateByFields(data map[string]interface{}, query QueryParam) bool {
43 | return Update(m, data, query)
44 | }
45 |
46 | func (m *DeployApply) List(query QueryParam) ([]DeployApply, bool) {
47 | var data []DeployApply
48 | ok := GetMulti(&data, query)
49 | return data, ok
50 | }
51 |
52 | func (m *DeployApply) Count(query QueryParam) (int, bool) {
53 | var count int
54 | ok := Count(m, &count, query)
55 | return count, ok
56 | }
57 |
58 | func (m *DeployApply) Delete() bool {
59 | return DeleteByPk(m)
60 | }
61 |
62 | func (m *DeployApply) Get(id int) bool {
63 | return GetByPk(m, id)
64 | }
65 |
--------------------------------------------------------------------------------
/model/deploy_build.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import(
8 | "time"
9 | )
10 |
11 | type DeployBuild struct {
12 | ID int `gorm:"primary_key"`
13 | ApplyId int `gorm:"type:int(11);not null;default:0"`
14 | StartTime int `gorm:"type:int(11);not null;default:0"`
15 | FinishTime int `gorm:"type:int(11);not null;default:0"`
16 | Status int `gorm:"type:int(11);not null;default:0"`
17 | Tar string `gorm:"type:varchar(2000);not null;default:''"`
18 | Output string `gorm:"type:text;not null"`
19 | Errmsg string `gorm:"type:text;not null"`
20 | Ctime int `gorm:"type:int(11);not null;default:0"`
21 | }
22 |
23 | func (m *DeployBuild) TableName() string {
24 | return "syd_deploy_build"
25 | }
26 |
27 | func (m *DeployBuild) GetByApplyId(id int) bool {
28 | return GetOne(m, QueryParam{
29 | Where: []WhereParam{
30 | WhereParam{
31 | Field: "apply_id",
32 | Prepare: id,
33 | },
34 | },
35 | })
36 | }
37 |
38 | func (m *DeployBuild) UpdateByFields(data map[string]interface{}, query QueryParam) bool {
39 | return Update(m, data, query)
40 | }
41 |
42 | func (m *DeployBuild) Create() bool {
43 | m.Ctime = int(time.Now().Unix())
44 | return Create(m)
45 | }
46 |
47 | func (m *DeployBuild) Delete() bool {
48 | return DeleteByPk(m)
49 | }
50 |
--------------------------------------------------------------------------------
/model/deploy_task.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import(
8 | "time"
9 | )
10 |
11 | type DeployTask struct {
12 | ID int `gorm:"primary_key"`
13 | ApplyId int `gorm:"type:int(11);not null;default:0"`
14 | GroupId int `gorm:"type:int(11);not null;default:0"`
15 | Status int `gorm:"type:int(11);not null;default:0"`
16 | Content string `gorm:"type:text;not null"`
17 | Ctime int `gorm:"type:int(11);not null;default:0"`
18 | }
19 |
20 | func (m *DeployTask) TableName() string {
21 | return "syd_deploy_task"
22 | }
23 |
24 | func (m *DeployTask) List(query QueryParam) ([]DeployTask, bool) {
25 | var data []DeployTask
26 | ok := GetMulti(&data, query)
27 | return data, ok
28 | }
29 |
30 | func (m *DeployTask) GetByApplyId(id int) bool {
31 | return GetOne(m, QueryParam{
32 | Where: []WhereParam{
33 | WhereParam{
34 | Field: "apply_id",
35 | Prepare: id,
36 | },
37 | },
38 | })
39 | }
40 |
41 | func (m *DeployTask) UpdateByFields(data map[string]interface{}, query QueryParam) bool {
42 | return Update(m, data, query)
43 | }
44 |
45 | func (m *DeployTask) Create() bool {
46 | m.Ctime = int(time.Now().Unix())
47 | return Create(m)
48 | }
49 |
50 | func (m *DeployTask) Delete(query QueryParam) bool {
51 | return Delete(m, query)
52 | }
53 |
--------------------------------------------------------------------------------
/model/model.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import (
8 | "fmt"
9 | "strings"
10 |
11 | "github.com/jinzhu/gorm"
12 | "github.com/dreamans/syncd"
13 | )
14 |
15 | type WhereParam struct {
16 | Field string
17 | Tag string
18 | Prepare interface{}
19 | }
20 |
21 | type QueryParam struct {
22 | Fields string
23 | Offset int
24 | Limit int
25 | Order string
26 | Where []WhereParam
27 | }
28 |
29 | func Create(model interface{}) bool {
30 | db := syncd.App.DB.DbHandler.Create(model)
31 | if err := db.Error; err != nil {
32 | syncd.App.Logger.Warning("mysql execute error: %s, sql [%v]", err.Error(), db.QueryExpr())
33 | return false
34 | }
35 | return true
36 | }
37 |
38 | func GetMulti(model interface{}, query QueryParam) bool {
39 | db := syncd.App.DB.DbHandler.Offset(query.Offset)
40 | if query.Limit > 0 {
41 | db = db.Limit(query.Limit)
42 | }
43 | if query.Fields != "" {
44 | db = db.Select(query.Fields)
45 | }
46 | if query.Order != "" {
47 | db = db.Order(query.Order)
48 | }
49 | db = parseWhereParam(db, query.Where)
50 | db.Find(model)
51 | if err := db.Error; err != nil {
52 | syncd.App.Logger.Warning("mysql query error: %s, sql[%v]", err.Error(), db.QueryExpr())
53 | return false
54 | }
55 | return true
56 | }
57 |
58 | func Count(model interface{}, count *int, query QueryParam) bool {
59 | db := syncd.App.DB.DbHandler.Model(model)
60 | db = parseWhereParam(db, query.Where)
61 | db = db.Count(count)
62 | if err := db.Error; err != nil {
63 | syncd.App.Logger.Warning("mysql query error: %s, sql[%v]", err.Error(), db.QueryExpr())
64 | return false
65 | }
66 | return true
67 | }
68 |
69 | func Delete(model interface{}, query QueryParam) bool {
70 | if len(query.Where) == 0 {
71 | syncd.App.Logger.Warning("mysql query error: delete failed, where conditions cannot be empty")
72 | return false
73 | }
74 | db := syncd.App.DB.DbHandler.Model(model)
75 | db = parseWhereParam(db, query.Where)
76 | db = db.Delete(model)
77 | if err := db.Error; err != nil {
78 | syncd.App.Logger.Warning("mysql query error: %s, sql[%v]", err.Error(), db.QueryExpr())
79 | return false
80 | }
81 | return true
82 | }
83 |
84 | func DeleteByPk(model interface{}) bool {
85 | db := syncd.App.DB.DbHandler.Model(model)
86 | db.Delete(model)
87 | if err := db.Error; err != nil {
88 | syncd.App.Logger.Warning("mysql query error: %s, sql[%v]", err.Error(), db.QueryExpr())
89 | return false
90 | }
91 | return true
92 | }
93 |
94 | func GetOne(model interface{}, query QueryParam) bool {
95 | db := syncd.App.DB.DbHandler.Model(model)
96 | if query.Fields != "" {
97 | db = db.Select(query.Fields)
98 | }
99 | db = parseWhereParam(db, query.Where)
100 | db = db.First(model)
101 | if err := db.Error; err != nil && !db.RecordNotFound() {
102 | syncd.App.Logger.Warning("mysql query error: %s, sql[%v]", err.Error(), db.QueryExpr())
103 | return false
104 | }
105 | return true
106 | }
107 |
108 | func GetByPk(model interface{}, id interface{}) bool {
109 | db := syncd.App.DB.DbHandler.Model(model)
110 | db.First(model, id)
111 | if err := db.Error; err != nil && !db.RecordNotFound() {
112 | syncd.App.Logger.Warning("mysql query error: %s sql[%v]", err.Error(), db.QueryExpr())
113 | return false
114 | }
115 | return true
116 | }
117 |
118 | func UpdateByPk(model interface{}) bool {
119 | db := syncd.App.DB.DbHandler.Model(model)
120 | db = db.Updates(model)
121 | if err := db.Error; err != nil {
122 | syncd.App.Logger.Warning("mysql query error: %s, sql[%v]", err.Error(), db.QueryExpr())
123 | return false
124 | }
125 | return true
126 | }
127 |
128 | func Update(model interface{}, data interface{}, query QueryParam) bool {
129 | db := syncd.App.DB.DbHandler.Model(model)
130 | db = parseWhereParam(db, query.Where)
131 | db = db.Updates(data)
132 | if err := db.Error; err != nil {
133 | syncd.App.Logger.Warning("mysql query error: %s, sql[%v]", err.Error(), db.QueryExpr())
134 | return false
135 | }
136 | return true
137 | }
138 |
139 | func parseWhereParam(db *gorm.DB, where []WhereParam) *gorm.DB {
140 | if len(where) == 0 {
141 | return db
142 | }
143 | var (
144 | plain []string
145 | prepare []interface{}
146 | )
147 | for _, w := range where {
148 | tag := w.Tag
149 | if tag == "" {
150 | tag = "="
151 | }
152 | var plainFmt string
153 | switch tag {
154 | case "IN":
155 | plainFmt = fmt.Sprintf("%s IN (?)", w.Field)
156 | default:
157 | plainFmt = fmt.Sprintf("%s %s ?", w.Field, tag)
158 | }
159 | plain = append(plain, plainFmt)
160 | prepare = append(prepare, w.Prepare)
161 | }
162 | return db.Where(strings.Join(plain, " AND "), prepare...)
163 | }
164 |
--------------------------------------------------------------------------------
/model/project.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import(
8 | "time"
9 | )
10 |
11 | type Project struct {
12 | ID int `gorm:"primary_key"`
13 | SpaceId int `gorm:"type:int(11);not null;default:0"`
14 | Name string `gorm:"type:varchar(100);not null;default:''"`
15 | Description string `gorm:"type:varchar(500);not null;default:''"`
16 | NeedAudit int `gorm:"type:int(11);not null;default:0"`
17 | Status int `gorm:"type:int(11);not null;default:0"`
18 | RepoUrl string `gorm:"type:varchar(500);not null;default:''"`
19 | DeployMode int `gorm:"type:int(11);not null;default:0"`
20 | RepoBranch string `gorm:"type:varchar(100);not null;default:''"`
21 | OnlineCluster string `gorm:"type:varchar(1000);not null;default:''"`
22 | DeployUser string `gorm:"type:varchar(100);not null;default:''"`
23 | DeployPath string `gorm:"type:varchar(500);not null;default:''"`
24 | BuildScript string `gorm:"type:text;not null"`
25 | BuildHookScript string `gorm:"type:text;not null"`
26 | DeployHookScript string `gorm:"type:text;not null"`
27 | PreDeployCmd string `gorm:"type:text;not null"`
28 | AfterDeployCmd string `gorm:"type:text;not null"`
29 | AuditNotice string `gorm:"type:varchar(2000);not null;default:''"`
30 | DeployNotice string `gorm:"type:varchar(2000);not null;default:''"`
31 | Ctime int `gorm:"type:int(11);not null;default:0"`
32 | }
33 |
34 | func (m *Project) TableName() string {
35 | return "syd_project"
36 | }
37 |
38 | func (m *Project) Create() bool {
39 | m.Ctime = int(time.Now().Unix())
40 | return Create(m)
41 | }
42 |
43 | func (m *Project) Update() bool {
44 | return UpdateByPk(m)
45 | }
46 |
47 | func (m *Project) UpdateByFields(data map[string]interface{}, query QueryParam) bool {
48 | return Update(m, data, query)
49 | }
50 |
51 | func (m *Project) List(query QueryParam) ([]Project, bool) {
52 | var data []Project
53 | ok := GetMulti(&data, query)
54 | return data, ok
55 | }
56 |
57 | func (m *Project) Count(query QueryParam) (int, bool) {
58 | var count int
59 | ok := Count(m, &count, query)
60 | return count, ok
61 | }
62 |
63 | func (m *Project) Delete() bool {
64 | return DeleteByPk(m)
65 | }
66 |
67 | func (m *Project) Get(id int) bool {
68 | return GetByPk(m, id)
69 | }
70 |
--------------------------------------------------------------------------------
/model/project_member.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import(
8 | "time"
9 | )
10 |
11 | type ProjectMember struct {
12 | ID int `gorm:"primary_key"`
13 | SpaceId int `gorm:"type:int(11);not null;default:0"`
14 | UserId int `gorm:"type:int(11);not null;default:0"`
15 | Ctime int `gorm:"type:int(11);not null;default:0"`
16 | }
17 |
18 | func (m *ProjectMember) TableName() string {
19 | return "syd_project_member"
20 | }
21 |
22 | func (m *ProjectMember) Create() bool {
23 | m.Ctime = int(time.Now().Unix())
24 | return Create(m)
25 | }
26 |
27 | func (m *ProjectMember) Update() bool {
28 | return UpdateByPk(m)
29 | }
30 |
31 | func (m *ProjectMember) List(query QueryParam) ([]ProjectMember, bool) {
32 | var data []ProjectMember
33 | ok := GetMulti(&data, query)
34 | return data, ok
35 | }
36 |
37 | func (m *ProjectMember) Count(query QueryParam) (int, bool) {
38 | var count int
39 | ok := Count(m, &count, query)
40 | return count, ok
41 | }
42 |
43 | func (m *ProjectMember) Delete() bool {
44 | return DeleteByPk(m)
45 | }
46 |
47 | func (m *ProjectMember) Get(id int) bool {
48 | return GetByPk(m, id)
49 | }
50 |
--------------------------------------------------------------------------------
/model/project_space.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import(
8 | "time"
9 | )
10 |
11 | type ProjectSpace struct {
12 | ID int `gorm:"primary_key"`
13 | Name string `gorm:"type:varchar(100);not null;default:''"`
14 | Description string `gorm:"type:varchar(2000);not null;default:''"`
15 | Ctime int `gorm:"type:int(11);not null;default:0"`
16 | }
17 |
18 | func (m *ProjectSpace) TableName() string {
19 | return "syd_project_space"
20 | }
21 |
22 | func (m *ProjectSpace) Create() bool {
23 | m.Ctime = int(time.Now().Unix())
24 | return Create(m)
25 | }
26 |
27 | func (m *ProjectSpace) Update() bool {
28 | return UpdateByPk(m)
29 | }
30 |
31 | func (m *ProjectSpace) List(query QueryParam) ([]ProjectSpace, bool) {
32 | var data []ProjectSpace
33 | ok := GetMulti(&data, query)
34 | return data, ok
35 | }
36 |
37 | func (m *ProjectSpace) Count(query QueryParam) (int, bool) {
38 | var count int
39 | ok := Count(m, &count, query)
40 | return count, ok
41 | }
42 |
43 | func (m *ProjectSpace) Delete() bool {
44 | return DeleteByPk(m)
45 | }
46 |
47 | func (m *ProjectSpace) Get(id int) bool {
48 | return GetByPk(m, id)
49 | }
50 |
--------------------------------------------------------------------------------
/model/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import(
8 | "time"
9 | )
10 |
11 | type Server struct {
12 | ID int `gorm:"primary_key"`
13 | GroupId int `gorm:"type:int(11);not null;default:0"`
14 | Name string `gorm:"type:varchar(100);not null;default:''"`
15 | Ip string `gorm:"type:varchar(100);not null;default:''"`
16 | SSHPort int `gorm:"type:int(11);not null;default:0"`
17 | Ctime int `gorm:"type:int(11);not null;default:0"`
18 | }
19 |
20 | func (m *Server) TableName() string {
21 | return "syd_server"
22 | }
23 |
24 | func (m *Server) Create() bool {
25 | m.Ctime = int(time.Now().Unix())
26 | return Create(m)
27 | }
28 |
29 | func (m *Server) Update() bool {
30 | return UpdateByPk(m)
31 | }
32 |
33 | func (m *Server) List(query QueryParam) ([]Server, bool) {
34 | var data []Server
35 | ok := GetMulti(&data, query)
36 | return data, ok
37 | }
38 |
39 | func (m *Server) Count(query QueryParam) (int, bool) {
40 | var count int
41 | ok := Count(m, &count, query)
42 | return count, ok
43 | }
44 |
45 | func (m *Server) Delete() bool {
46 | return DeleteByPk(m)
47 | }
48 |
49 | func (m *Server) Get(id int) bool {
50 | return GetByPk(m, id)
51 | }
52 |
--------------------------------------------------------------------------------
/model/server_group.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import (
8 | "time"
9 | )
10 |
11 | type ServerGroup struct {
12 | ID int `gorm:"primary_key"`
13 | Name string `gorm:"type:varchar(100);not null;default:''"`
14 | Ctime int `gorm:"type:int(11);not null;default:0"`
15 | }
16 |
17 | func (m *ServerGroup) TableName() string {
18 | return "syd_server_group"
19 | }
20 |
21 | func (m *ServerGroup) Create() bool {
22 | m.Ctime = int(time.Now().Unix())
23 | return Create(m)
24 | }
25 |
26 | func (m *ServerGroup) Update() bool {
27 | return UpdateByPk(m)
28 | }
29 |
30 | func (m *ServerGroup) List(query QueryParam) ([]ServerGroup, bool) {
31 | var data []ServerGroup
32 | ok := GetMulti(&data, query)
33 | return data, ok
34 | }
35 |
36 | func (m *ServerGroup) Count(query QueryParam) (int, bool) {
37 | var count int
38 | ok := Count(m, &count, query)
39 | return count, ok
40 | }
41 |
42 | func (m *ServerGroup) Delete() bool {
43 | return DeleteByPk(m)
44 | }
45 |
46 | func (m *ServerGroup) Get(id int) bool {
47 | return GetByPk(m, id)
48 | }
49 |
--------------------------------------------------------------------------------
/model/user.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import(
8 | "time"
9 | )
10 |
11 | type User struct {
12 | ID int `gorm:"primary_key"`
13 | RoleId int `gorm:"type:int(11);not null;default:0"`
14 | Username string `gorm:"type:varchar(20);not null;default:''"`
15 | Password string `gorm:"type:char(32);not null;default:''"`
16 | Salt string `gorm:"type:char(10);not null;default:''"`
17 | Truename string `gorm:"type:varchar(20);not null;default:''"`
18 | Mobile string `gorm:"type:varchar(20);not null;default:''"`
19 | Email string `gorm:"type:varchar(500);not null;default:''"`
20 | Status int `gorm:"type:int(11);not null;default:0"`
21 | LastLoginTime int `gorm:"type:int(11);not null;default:0"`
22 | LastLoginIp string `gorm:"type:varchar(50);not null;default:''"`
23 | Ctime int `gorm:"type:int(11);not null;default:0"`
24 | }
25 |
26 | func (m *User) TableName() string {
27 | return "syd_user"
28 | }
29 |
30 | func (m *User) Create() bool {
31 | m.Ctime = int(time.Now().Unix())
32 | return Create(m)
33 | }
34 |
35 | func (m *User) UpdateByFields(data map[string]interface{}, query QueryParam) bool {
36 | return Update(m, data, query)
37 | }
38 |
39 | func (m *User) List(query QueryParam) ([]User, bool) {
40 | var data []User
41 | ok := GetMulti(&data, query)
42 | return data, ok
43 | }
44 |
45 | func (m *User) Count(query QueryParam) (int, bool) {
46 | var count int
47 | ok := Count(m, &count, query)
48 | return count, ok
49 | }
50 |
51 | func (m *User) Delete() bool {
52 | return DeleteByPk(m)
53 | }
54 |
55 | func (m *User) Get(id int) bool {
56 | return GetByPk(m, id)
57 | }
58 |
59 | func (m *User) GetOne(query QueryParam) bool {
60 | return GetOne(m, query)
61 | }
62 |
--------------------------------------------------------------------------------
/model/user_role.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import(
8 | "time"
9 | )
10 |
11 | type UserRole struct {
12 | ID int `gorm:"primary_key"`
13 | Name string `gorm:"type:varchar(100);not null;default:''"`
14 | Privilege string `gorm:"type:varchar(2000);not null;default:''"`
15 | Ctime int `gorm:"type:int(11);not null;default:0"`
16 | }
17 |
18 | func (m *UserRole) TableName() string {
19 | return "syd_user_role"
20 | }
21 |
22 | func (m *UserRole) Create() bool {
23 | m.Ctime = int(time.Now().Unix())
24 | return Create(m)
25 | }
26 |
27 | func (m *UserRole) Update() bool {
28 | return UpdateByPk(m)
29 | }
30 |
31 | func (m *UserRole) List(query QueryParam) ([]UserRole, bool) {
32 | var data []UserRole
33 | ok := GetMulti(&data, query)
34 | return data, ok
35 | }
36 |
37 | func (m *UserRole) Count(query QueryParam) (int, bool) {
38 | var count int
39 | ok := Count(m, &count, query)
40 | return count, ok
41 | }
42 |
43 | func (m *UserRole) Delete() bool {
44 | return DeleteByPk(m)
45 | }
46 |
47 | func (m *UserRole) Get(id int) bool {
48 | return GetByPk(m, id)
49 | }
50 |
--------------------------------------------------------------------------------
/model/user_token.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import(
8 | "time"
9 | )
10 |
11 | type UserToken struct {
12 | ID int `gorm:"primary_key"`
13 | UserId int `gorm:"type:int(11);not null;default:0"`
14 | Token string `gorm:"type:varchar(100);not null;default:''"`
15 | Expire int `gorm:"type:int(11);not null;default:0"`
16 | Ctime int `gorm:"type:int(11);not null;default:0"`
17 | }
18 |
19 | func (m *UserToken) TableName() string {
20 | return "syd_user_token"
21 | }
22 |
23 | func (m *UserToken) Create() bool {
24 | m.Ctime = int(time.Now().Unix())
25 | return Create(m)
26 | }
27 |
28 | func (m *UserToken) UpdateByFields(data map[string]interface{}, query QueryParam) bool {
29 | return Update(m, data, query)
30 | }
31 |
32 | func (m *UserToken) Update() bool {
33 | return UpdateByPk(m)
34 | }
35 |
36 | func (m *UserToken) List(query QueryParam) ([]UserToken, bool) {
37 | var data []UserToken
38 | ok := GetMulti(&data, query)
39 | return data, ok
40 | }
41 |
42 | func (m *UserToken) Count(query QueryParam) (int, bool) {
43 | var count int
44 | ok := Count(m, &count, query)
45 | return count, ok
46 | }
47 |
48 | func (m *UserToken) Delete() bool {
49 | return DeleteByPk(m)
50 | }
51 |
52 | func (m *UserToken) DeleteByFields(query QueryParam) bool {
53 | return Delete(m, query)
54 | }
55 |
56 | func (m *UserToken) Get(id int) bool {
57 | return GetByPk(m, id)
58 | }
59 |
60 | func (m *UserToken) GetOne(query QueryParam) bool {
61 | return GetOne(m, query)
62 | }
63 |
--------------------------------------------------------------------------------
/module/deploy/build.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package deploy
6 |
7 | import (
8 | "errors"
9 | "time"
10 |
11 | "github.com/dreamans/syncd/model"
12 | )
13 |
14 | type Build struct{
15 | ID int `json:"id"`
16 | ApplyId int `json:"apply_id"`
17 | StartTime int `json:"start_time"`
18 | FinishTime int `json:"finish_time"`
19 | Status int `json:"status"`
20 | Tar string `json:"tar"`
21 | Output string `json:"Output"`
22 | Errmsg string `json:"errmsg"`
23 | Ctime int `json:"ctime"`
24 | }
25 |
26 | const (
27 | BUILD_STATUS_NONE = 0
28 | BUILD_STATUS_START = 1
29 | BUILD_STATUS_SUCCESS = 2
30 | BUILD_STATUS_FAILED = 3
31 | )
32 |
33 | func (b *Build) Create() error {
34 | build := &model.DeployBuild{
35 | ApplyId: b.ApplyId,
36 | Status: b.Status,
37 | StartTime: int(time.Now().Unix()),
38 | }
39 | if ok := build.Create(); !ok {
40 | return errors.New("create deploy build failed")
41 | }
42 | return nil
43 | }
44 |
45 | func (b *Build) CreateFull() error {
46 | build := &model.DeployBuild{
47 | ApplyId: b.ApplyId,
48 | StartTime: b.StartTime,
49 | FinishTime: b.FinishTime,
50 | Status: b.Status,
51 | Tar: b.Tar,
52 | Output: b.Output,
53 | Errmsg: b.Errmsg,
54 | }
55 | if ok := build.Create(); !ok {
56 | return errors.New("create deploy build failed")
57 | }
58 | return nil
59 | }
60 |
61 | func (b *Build) Detail() error {
62 | build := &model.DeployBuild{}
63 | if ok := build.GetByApplyId(b.ApplyId); !ok {
64 | return errors.New("get deploy build detail failed")
65 | }
66 | if build.ID == 0 {
67 | build.Status = BUILD_STATUS_NONE
68 | return nil
69 | }
70 | b.ID = build.ID
71 | b.Status = build.Status
72 | b.StartTime = build.StartTime
73 | b.FinishTime = build.FinishTime
74 | b.Tar = build.Tar
75 | b.Output = build.Output
76 | b.Errmsg = build.Errmsg
77 | b.Ctime = build.Ctime
78 |
79 | return nil
80 | }
81 |
82 | func (b *Build) Exists() (bool, error) {
83 | if err := b.Detail(); err != nil {
84 | return false, err
85 | }
86 | if b.ID == 0 {
87 | return false, nil
88 | }
89 | return true, nil
90 | }
91 |
92 | func (b *Build) Finish() error {
93 | build := &model.DeployBuild{}
94 | updateData := map[string]interface{}{
95 | "status": b.Status,
96 | "tar": b.Tar,
97 | "output": b.Output,
98 | "finish_time": int(time.Now().Unix()),
99 | "errmsg": b.Errmsg,
100 | }
101 | if ok := build.UpdateByFields(updateData, model.QueryParam{
102 | Where:[]model.WhereParam{
103 | model.WhereParam{
104 | Field: "apply_id",
105 | Prepare: b.ApplyId,
106 | },
107 | },
108 | }); !ok {
109 | return errors.New("update deploy build failed")
110 | }
111 | return nil
112 | }
113 |
114 | func (b *Build) Delete() error {
115 | build := &model.DeployBuild{
116 | ID: b.ID,
117 | }
118 | if ok := build.Delete(); !ok {
119 | return errors.New("remove deploy build failed")
120 | }
121 | return nil
122 | }
123 |
--------------------------------------------------------------------------------
/module/deploy/deploy.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package deploy
6 |
7 | import (
8 | "errors"
9 |
10 | "github.com/dreamans/syncd/model"
11 | )
12 |
13 | type Deploy struct{
14 | ID int `json:"id"`
15 | ApplyId int `json:"apply_id"`
16 | GroupId int `json:"group_id"`
17 | Status int `json:"status"`
18 | Content string `json:"content"`
19 | Ctime int `json:"ctime"`
20 | }
21 |
22 | const (
23 | DEPLOY_STATUS_NONE = 0
24 | DEPLOY_STATUS_START = 1
25 | DEPLOY_STATUS_SUCCESS = 2
26 | DEPLOY_STATUS_FAILED = 3
27 | )
28 |
29 | func (d *Deploy) TaskList() ([]Deploy, error) {
30 | dt := &model.DeployTask{}
31 | list, ok := dt.List(model.QueryParam{
32 | Fields: "id, apply_id, group_id, status, content, ctime",
33 | Where: []model.WhereParam{
34 | model.WhereParam{
35 | Field: "apply_id",
36 | Prepare: d.ApplyId,
37 | },
38 | },
39 | })
40 | if !ok {
41 | return nil, errors.New("get deploy task list failed")
42 | }
43 | var (
44 | deployList []Deploy
45 | )
46 | for _, l := range list {
47 | deployList = append(deployList, Deploy{
48 | ID: l.ID,
49 | ApplyId: l.ApplyId,
50 | GroupId: l.GroupId,
51 | Status: l.Status,
52 | Content: l.Content,
53 | Ctime: l.Ctime,
54 | })
55 | }
56 | return deployList, nil
57 | }
58 |
59 | func (d *Deploy) Create() error {
60 | deploy := model.DeployTask{
61 | ApplyId: d.ApplyId,
62 | GroupId: d.GroupId,
63 | Status: d.Status,
64 | }
65 | if ok := deploy.Create(); !ok {
66 | return errors.New("create deploy task failed")
67 | }
68 | return nil
69 | }
70 |
71 | func (d *Deploy) UpdateStatus() error {
72 | dt := &model.DeployTask{}
73 | updateData := map[string]interface{}{
74 | "status": d.Status,
75 | }
76 | if ok := dt.UpdateByFields(updateData, model.QueryParam{
77 | Where: []model.WhereParam{
78 | model.WhereParam{
79 | Field: "apply_id",
80 | Prepare: d.ApplyId,
81 | },
82 | model.WhereParam{
83 | Field: "group_id",
84 | Prepare: d.GroupId,
85 | },
86 | },
87 | }); !ok {
88 | return errors.New("update deploy task result failed")
89 | }
90 | return nil
91 | }
92 |
93 | func (d *Deploy) UpdateResult() error {
94 | dt := &model.DeployTask{}
95 | updateData := map[string]interface{}{
96 | "status": d.Status,
97 | "content": d.Content,
98 | }
99 | if ok := dt.UpdateByFields(updateData, model.QueryParam{
100 | Where: []model.WhereParam{
101 | model.WhereParam{
102 | Field: "apply_id",
103 | Prepare: d.ApplyId,
104 | },
105 | model.WhereParam{
106 | Field: "group_id",
107 | Prepare: d.GroupId,
108 | },
109 | },
110 | }); !ok {
111 | return errors.New("update deploy task result failed")
112 | }
113 | return nil
114 | }
115 |
116 | func (d *Deploy) DeleteByApplyId() error {
117 | dep := &model.DeployTask{
118 | ApplyId: d.ApplyId,
119 | }
120 | if ok := dep.Delete(model.QueryParam{
121 | Where: []model.WhereParam{
122 | model.WhereParam{
123 | Field: "apply_id",
124 | Prepare: d.ApplyId,
125 | },
126 | },
127 | }); !ok {
128 | return errors.New("remove deploy task failed")
129 | }
130 | return nil
131 | }
--------------------------------------------------------------------------------
/module/project/space.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package project
6 |
7 | import (
8 | "errors"
9 | "fmt"
10 |
11 | "github.com/dreamans/syncd/model"
12 | )
13 |
14 | type Space struct {
15 | ID int `json:"id"`
16 | Name string `json:"name"`
17 | Description string `json:"description"`
18 | Ctime int `json:"ctime"`
19 | }
20 |
21 | func SpaceListByIds(spaceIds []int) ([]Space, error) {
22 | s := &Space{}
23 | return s.List(spaceIds, "", 0, 999)
24 | }
25 |
26 | func (s *Space) Delete() error {
27 | space := &model.ProjectSpace{
28 | ID: s.ID,
29 | }
30 | if ok := space.Delete(); !ok {
31 | return errors.New("delete project space failed")
32 | }
33 | return nil
34 | }
35 |
36 | func (s *Space) Detail() error {
37 | space := &model.ProjectSpace{}
38 | if ok := space.Get(s.ID); !ok {
39 | return errors.New("get project space detail failed")
40 | }
41 | if space.ID == 0 {
42 | return errors.New("project space detail not exists")
43 | }
44 |
45 | s.ID = space.ID
46 | s.Name = space.Name
47 | s.Description = space.Description
48 | s.Ctime = space.Ctime
49 |
50 | return nil
51 | }
52 |
53 | func (s *Space) CreateOrUpdate() error {
54 | space := &model.ProjectSpace{
55 | ID: s.ID,
56 | Name: s.Name,
57 | Description: s.Description,
58 | }
59 | if space.ID == 0 {
60 | if ok := space.Create(); !ok {
61 | return errors.New("project space create failed")
62 | }
63 | s.ID = space.ID
64 | } else {
65 | if ok := space.Update(); !ok {
66 | return errors.New("project space update failed")
67 | }
68 | }
69 | return nil
70 | }
71 |
72 | func (s *Space) List(spaceIds []int, keyword string, offset, limit int) ([]Space, error) {
73 | space := &model.ProjectSpace{}
74 | list, ok := space.List(model.QueryParam{
75 | Fields: "id, name, description, ctime",
76 | Offset: offset,
77 | Limit: limit,
78 | Order: "id DESC",
79 | Where: s.parseWhereConds(spaceIds, keyword),
80 | })
81 | if !ok {
82 | return nil, errors.New("get project space list failed")
83 | }
84 |
85 | var spaceList []Space
86 | for _, l := range list {
87 | spaceList = append(spaceList, Space{
88 | ID: l.ID,
89 | Name: l.Name,
90 | Description: l.Description,
91 | Ctime: l.Ctime,
92 | })
93 | }
94 | return spaceList, nil
95 | }
96 |
97 | func (s *Space) Total(spaceIds []int, keyword string) (int, error) {
98 | space := &model.ProjectSpace{}
99 | total, ok := space.Count(model.QueryParam{
100 | Where: s.parseWhereConds(spaceIds, keyword),
101 | })
102 | if !ok {
103 | return 0, errors.New("get project space count failed")
104 | }
105 | return total, nil
106 | }
107 |
108 | func (s *Space) parseWhereConds(spaceIds []int, keyword string) []model.WhereParam {
109 | var where []model.WhereParam
110 | if keyword != "" {
111 | where = append(where, model.WhereParam{
112 | Field: "name",
113 | Tag: "LIKE",
114 | Prepare: fmt.Sprintf("%%%s%%", keyword),
115 | })
116 | }
117 | where = append(where, model.WhereParam{
118 | Field: "id",
119 | Tag: "IN",
120 | Prepare: spaceIds,
121 | })
122 | return where
123 | }
124 |
--------------------------------------------------------------------------------
/module/server/group.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package server
6 |
7 | import (
8 | "errors"
9 | "fmt"
10 |
11 | "github.com/dreamans/syncd/util/gois"
12 | "github.com/dreamans/syncd/model"
13 | )
14 |
15 | type Group struct {
16 | ID int `json:"id"`
17 | Name string `json:"name"`
18 | Ctime int `json:"ctime"`
19 | }
20 |
21 | func GroupGetMapByIds(ids []int) (map[int]Group, error) {
22 | if len(ids) == 0 {
23 | return nil, nil
24 | }
25 | group := &model.ServerGroup{}
26 | groupList, ok := group.List(model.QueryParam{
27 | Where: []model.WhereParam{
28 | model.WhereParam{
29 | Field: "id",
30 | Tag: "IN",
31 | Prepare: ids,
32 | },
33 | },
34 | })
35 | if !ok {
36 | return nil, errors.New("get server group list failed")
37 | }
38 | groupMap := make(map[int]Group)
39 | for _, l := range groupList{
40 | groupMap[l.ID] = Group{
41 | ID: l.ID,
42 | Name: l.Name,
43 | Ctime: l.Ctime,
44 | }
45 | }
46 | return groupMap, nil
47 | }
48 |
49 | func (g *Group) Create() error {
50 | serverGroup := model.ServerGroup{
51 | Name: g.Name,
52 | }
53 | if ok := serverGroup.Create(); !ok {
54 | return errors.New("create server group data failed")
55 | }
56 | return nil
57 | }
58 |
59 | func (g *Group) Update() error {
60 | serverGroup := model.ServerGroup{
61 | ID: g.ID,
62 | Name: g.Name,
63 | }
64 | if ok := serverGroup.Update(); !ok {
65 | return errors.New("update server group data failed")
66 | }
67 | return nil
68 | }
69 |
70 | func (g *Group) List(keyword string, offset, limit int) ([]Group, error) {
71 | group := model.ServerGroup{}
72 | list, ok := group.List(model.QueryParam{
73 | Fields: "id, name, ctime",
74 | Offset: offset,
75 | Limit: limit,
76 | Order: "id DESC",
77 | Where: g.parseWhereConds(keyword),
78 | })
79 | if !ok {
80 | return nil, errors.New("get server group list failed")
81 | }
82 |
83 | var groupList []Group
84 | for _, l := range list {
85 | groupList = append(groupList, Group{
86 | ID: l.ID,
87 | Name: l.Name,
88 | Ctime: l.Ctime,
89 | })
90 | }
91 | return groupList, nil
92 | }
93 |
94 | func (g *Group) Total(keyword string) (int, error) {
95 | group := model.ServerGroup{}
96 | total, ok := group.Count(model.QueryParam{
97 | Where: g.parseWhereConds(keyword),
98 | })
99 | if !ok {
100 | return 0, errors.New("get server group count failed")
101 | }
102 | return total, nil
103 | }
104 |
105 | func (g *Group) Delete() error {
106 | group := &model.ServerGroup{
107 | ID: g.ID,
108 | }
109 | if ok := group.Delete(); !ok {
110 | return errors.New("delete server group failed")
111 | }
112 | return nil
113 | }
114 |
115 | func (g *Group) Detail() error {
116 | group := model.ServerGroup{}
117 | if ok := group.Get(g.ID); !ok {
118 | return errors.New("get server group detail failed")
119 | }
120 | if group.ID == 0 {
121 | return errors.New("server group not exists")
122 | }
123 |
124 | g.ID = group.ID
125 | g.Name = group.Name
126 | g.Ctime = group.Ctime
127 |
128 | return nil
129 | }
130 |
131 | func (g *Group) parseWhereConds(keyword string) []model.WhereParam {
132 | var where []model.WhereParam
133 | if keyword != "" {
134 | if gois.IsInteger(keyword) {
135 | where = append(where, model.WhereParam{
136 | Field: "id",
137 | Prepare: keyword,
138 | })
139 | } else {
140 | where = append(where, model.WhereParam{
141 | Field: "name",
142 | Tag: "LIKE",
143 | Prepare: fmt.Sprintf("%%%s%%", keyword),
144 | })
145 | }
146 | }
147 | return where
148 | }
149 |
--------------------------------------------------------------------------------
/module/server/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package server
6 |
7 | import (
8 | "errors"
9 | "fmt"
10 |
11 | "github.com/dreamans/syncd/util/gois"
12 | "github.com/dreamans/syncd/model"
13 | )
14 |
15 | type Server struct {
16 | ID int `json:"id"`
17 | GroupId int `json:"group_id"`
18 | GroupName string `json:"group_name"`
19 | Name string `json:"name"`
20 | Ip string `json:"ip"`
21 | SSHPort int `json:"ssh_port"`
22 | Ctime int `json:"ctime"`
23 | }
24 |
25 | func ServerGetListByGroupIds(groupIds []int) ([]Server, error){
26 | server := &model.Server{}
27 | list, ok := server.List(model.QueryParam{
28 | Fields: "id, group_id, name, ip, ssh_port, ctime",
29 | Where: []model.WhereParam{
30 | model.WhereParam{
31 | Field: "group_id",
32 | Tag: "IN",
33 | Prepare: groupIds,
34 | },
35 | },
36 | })
37 | if !ok {
38 | return nil, errors.New("get server list failed")
39 | }
40 | serList := []Server{}
41 | for _, s := range list {
42 | serList = append(serList, Server{
43 | ID: s.ID,
44 | GroupId: s.GroupId,
45 | Name: s.Name,
46 | Ip: s.Ip,
47 | SSHPort: s.SSHPort,
48 | })
49 | }
50 | return serList, nil
51 | }
52 |
53 | func (s *Server) CreateOrUpdate() error {
54 | server := &model.Server{
55 | ID: s.ID,
56 | GroupId: s.GroupId,
57 | Name: s.Name,
58 | Ip: s.Ip,
59 | SSHPort: s.SSHPort,
60 | }
61 | if server.ID == 0 {
62 | if ok := server.Create(); !ok {
63 | return errors.New("create server failed")
64 | }
65 | } else {
66 | if ok := server.Update(); !ok {
67 | return errors.New("update server failed")
68 | }
69 | }
70 | return nil
71 | }
72 |
73 | func (s *Server) List(keyword string, offset, limit int) ([]Server, error) {
74 | server := &model.Server{}
75 | list, ok := server.List(model.QueryParam{
76 | Fields: "id, group_id, name, ip, ssh_port, ctime",
77 | Offset: offset,
78 | Limit: limit,
79 | Order: "id DESC",
80 | Where: s.parseWhereConds(keyword),
81 | })
82 | if !ok {
83 | return nil, errors.New("get server list failed")
84 | }
85 |
86 | var (
87 | serverList []Server
88 | groupIds []int
89 | )
90 | for _, l := range list {
91 | serverList = append(serverList, Server{
92 | ID: l.ID,
93 | GroupId: l.GroupId,
94 | Name: l.Name,
95 | Ip: l.Ip,
96 | SSHPort: l.SSHPort,
97 | Ctime: l.Ctime,
98 | })
99 | groupIds = append(groupIds, l.GroupId)
100 | }
101 |
102 | groupMap, err := GroupGetMapByIds(groupIds)
103 | if err != nil {
104 | return nil, errors.New("get server group map failed")
105 | }
106 | for k, l := range serverList {
107 | if g, exists := groupMap[l.GroupId]; exists {
108 | serverList[k].GroupName = g.Name
109 | }
110 | }
111 |
112 | return serverList, nil
113 | }
114 |
115 | func (s *Server) Total(keyword string) (int, error) {
116 | server := model.Server{}
117 | total, ok := server.Count(model.QueryParam{
118 | Where: s.parseWhereConds(keyword),
119 | })
120 | if !ok {
121 | return 0, errors.New("get server count failed")
122 | }
123 | return total, nil
124 | }
125 |
126 | func (s *Server) Delete() error {
127 | server := &model.Server{
128 | ID: s.ID,
129 | }
130 | if ok := server.Delete(); !ok {
131 | return errors.New("delete server failed")
132 | }
133 | return nil
134 | }
135 |
136 | func (s *Server) Detail() error {
137 | server := &model.Server{}
138 | if ok := server.Get(s.ID); !ok {
139 | return errors.New("get server detail failed")
140 | }
141 | if server.ID == 0 {
142 | return errors.New("server not exists")
143 | }
144 |
145 | s.ID = server.ID
146 | s.GroupId = server.GroupId
147 | s.Name = server.Name
148 | s.Ip = server.Ip
149 | s.SSHPort = server.SSHPort
150 | s.Ctime = server.Ctime
151 |
152 | return nil
153 | }
154 |
155 | func (s *Server) parseWhereConds(keyword string) []model.WhereParam {
156 | var where []model.WhereParam
157 | if keyword != "" {
158 | if gois.IsInteger(keyword) {
159 | where = append(where, model.WhereParam{
160 | Field: "id",
161 | Prepare: keyword,
162 | })
163 | } else {
164 | if gois.IsIp(keyword) {
165 | where = append(where, model.WhereParam{
166 | Field: "ip",
167 | Prepare: keyword,
168 | })
169 | } else {
170 | where = append(where, model.WhereParam{
171 | Field: "name",
172 | Tag: "LIKE",
173 | Prepare: fmt.Sprintf("%%%s%%", keyword),
174 | })
175 | }
176 | }
177 | }
178 | return where
179 | }
180 |
--------------------------------------------------------------------------------
/module/user/login.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package user
6 |
7 | import (
8 | "errors"
9 | "fmt"
10 | "time"
11 | "strings"
12 |
13 | "github.com/dreamans/syncd"
14 | "github.com/dreamans/syncd/util/gostring"
15 | "github.com/dreamans/syncd/util/goaes"
16 | )
17 |
18 | type Login struct {
19 | UserId int
20 | RoleId int
21 | Username string
22 | Password string
23 | Email string
24 | Truename string
25 | Mobile string
26 | Token string
27 | }
28 |
29 | func (login *Login) Logout() error {
30 | token := &Token{
31 | UserId: login.UserId,
32 | }
33 | return token.DeleteByUserId()
34 | }
35 |
36 | func (login *Login) Login() error {
37 | u := &User{}
38 | if login.Username != "" {
39 | u.Username = login.Username
40 | }
41 | if login.Email != "" {
42 | u.Email = login.Email
43 | }
44 | if err := u.Detail(); err != nil {
45 | return errors.New("username or password incorrect")
46 | }
47 | loginPassword := gostring.StrMd5(gostring.JoinStrings(login.Password, u.Salt))
48 | if u.Password != loginPassword {
49 | return errors.New("password incorrect")
50 | }
51 |
52 | if u.Status != 1 {
53 | return errors.New("user is locked")
54 | }
55 |
56 | login.UserId = u.ID
57 | if err := login.createToken(); err != nil {
58 | return errors.New("token create failed")
59 | }
60 |
61 | return nil
62 | }
63 |
64 | func (login *Login) ValidateToken() error {
65 | authTokenBytes, err := gostring.Base64UrlDecode(login.Token)
66 | if err != nil {
67 | return errors.New("token check failed, can not decode raw token")
68 | }
69 | tokenValBytes, err := goaes.Decrypt(syncd.App.CipherKey, authTokenBytes)
70 | if err != nil {
71 | return errors.New("token check failed, can not decrypt raw token")
72 | }
73 | tokenArr := strings.Split(string(tokenValBytes), "\t")
74 | if len(tokenArr) != 2 {
75 | return errors.New("token check failed, len wrong")
76 | }
77 | token := &Token{
78 | UserId: gostring.Str2Int(tokenArr[0]),
79 | Token: tokenArr[1],
80 | }
81 | if ok := token.ValidateToken(); !ok {
82 | return errors.New("token check failed, maybe your account is logged in on another device or token expired")
83 | }
84 |
85 | //get user detail
86 | user := &User{
87 | ID: token.UserId,
88 | }
89 | if err := user.Detail(); err != nil {
90 | return errors.New("token check failed, user detail get failed")
91 | }
92 |
93 | if user.Status != 1 {
94 | return errors.New("user is locked")
95 | }
96 |
97 | login.UserId = user.ID
98 | login.Username = user.Username
99 | login.Email = user.Email
100 | login.Truename = user.Truename
101 | login.Mobile = user.Mobile
102 | login.RoleId = user.RoleId
103 |
104 | return nil
105 | }
106 |
107 | func (login *Login) createToken() error {
108 | loginKey := gostring.StrRandom(40)
109 | loginRaw := fmt.Sprintf("%d\t%s", login.UserId, loginKey)
110 | var (
111 | err error
112 | tokenBytes []byte
113 | )
114 | tokenBytes, err = goaes.Encrypt(syncd.App.CipherKey, []byte(loginRaw))
115 | if err != nil {
116 | return err
117 | }
118 | login.Token = gostring.Base64UrlEncode(tokenBytes)
119 |
120 | token := &Token{
121 | UserId: login.UserId,
122 | Token: loginKey,
123 | Expire: int(time.Now().Unix()) + 86400 * 30,
124 | }
125 | if err := token.CreateOrUpdate(); err != nil {
126 | return err
127 | }
128 | return nil
129 | }
130 |
131 |
--------------------------------------------------------------------------------
/module/user/role.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package user
6 |
7 | import (
8 | "errors"
9 | "fmt"
10 |
11 | "github.com/dreamans/syncd/util/gostring"
12 | "github.com/dreamans/syncd/util/gois"
13 | "github.com/dreamans/syncd/model"
14 | )
15 |
16 | type Role struct {
17 | ID int `json:"id"`
18 | Name string `json:"name"`
19 | Privilege []int `json:"privilege"`
20 | Ctime int `json:"ctime"`
21 | }
22 |
23 | func RoleGetMapByIds(ids []int) (map[int]Role, error) {
24 | if len(ids) == 0 {
25 | return nil, nil
26 | }
27 | role := &model.UserRole{}
28 | list, ok := role.List(model.QueryParam{
29 | Where: []model.WhereParam{
30 | model.WhereParam{
31 | Field: "id",
32 | Tag: "IN",
33 | Prepare: ids,
34 | },
35 | },
36 | })
37 | if !ok {
38 | return nil, errors.New("get user role maps failed")
39 | }
40 | roleMap := make(map[int]Role)
41 | for _, l := range list {
42 | roleMap[l.ID] = Role{
43 | ID: l.ID,
44 | Name: l.Name,
45 | }
46 | }
47 | return roleMap, nil
48 | }
49 |
50 | func (r *Role) Detail() error {
51 | role := &model.UserRole{}
52 | if ok := role.Get(r.ID); !ok {
53 | return errors.New("get user role detail failed")
54 | }
55 | if role.ID == 0 {
56 | return errors.New("user role not exists")
57 | }
58 |
59 | r.ID = role.ID
60 | r.Name = role.Name
61 | r.Privilege = gostring.StrSplit2IntSlice(role.Privilege, ",")
62 | r.Ctime = role.Ctime
63 |
64 | return nil
65 | }
66 |
67 | func (r *Role) CreateOrUpdate() error {
68 | role := &model.UserRole{
69 | ID: r.ID,
70 | Name: r.Name,
71 | Privilege: gostring.JoinIntSlice2String(r.Privilege, ","),
72 | }
73 | if role.ID == 0 {
74 | if ok := role.Create(); !ok {
75 | return errors.New("create user role data failed")
76 | }
77 | } else {
78 | if ok := role.Update(); !ok {
79 | return errors.New("update user role failed")
80 | }
81 | }
82 | return nil
83 | }
84 |
85 | func (r *Role) List(keyword string, offset, limit int) ([]Role, error) {
86 | role := &model.UserRole{}
87 | list, ok := role.List(model.QueryParam{
88 | Fields: "id, name, ctime",
89 | Offset: offset,
90 | Limit: limit,
91 | Order: "id ASC",
92 | Where: r.parseWhereConds(keyword),
93 | })
94 | if !ok {
95 | return nil, errors.New("get user role list failed")
96 | }
97 |
98 | var roleList []Role
99 | for _, l := range list {
100 | roleList = append(roleList, Role{
101 | ID: l.ID,
102 | Name: l.Name,
103 | Ctime: l.Ctime,
104 | })
105 | }
106 | return roleList, nil
107 | }
108 |
109 | func (r *Role) Total(keyword string) (int, error) {
110 | role := &model.UserRole{}
111 | total, ok := role.Count(model.QueryParam{
112 | Where: r.parseWhereConds(keyword),
113 | })
114 | if !ok {
115 | return 0, errors.New("get user role count failed")
116 | }
117 | return total, nil
118 | }
119 |
120 | func (r *Role) Delete() error {
121 | role := &model.UserRole{
122 | ID: r.ID,
123 | }
124 | if ok := role.Delete(); !ok {
125 | return errors.New("delete user role failed")
126 | }
127 | return nil
128 | }
129 |
130 | func (r *Role) parseWhereConds(keyword string) []model.WhereParam {
131 | var where []model.WhereParam
132 | if keyword != "" {
133 | if gois.IsInteger(keyword) {
134 | where = append(where, model.WhereParam{
135 | Field: "id",
136 | Prepare: keyword,
137 | })
138 | } else {
139 | where = append(where, model.WhereParam{
140 | Field: "name",
141 | Tag: "LIKE",
142 | Prepare: fmt.Sprintf("%%%s%%", keyword),
143 | })
144 | }
145 | }
146 | return where
147 | }
148 |
--------------------------------------------------------------------------------
/module/user/token.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package user
6 |
7 | import (
8 | "errors"
9 | "time"
10 |
11 | "github.com/dreamans/syncd/model"
12 | )
13 |
14 | type Token struct {
15 | ID int
16 | UserId int
17 | Token string
18 | Expire int
19 | }
20 |
21 | func (t *Token) DeleteByUserId() error {
22 | token := &model.UserToken{}
23 | if ok := token.DeleteByFields(model.QueryParam{
24 | Where: []model.WhereParam{
25 | model.WhereParam{
26 | Field: "user_id",
27 | Prepare: t.UserId,
28 | },
29 | },
30 | }); !ok {
31 | return errors.New("delete user token failed")
32 | }
33 | return nil
34 | }
35 |
36 | func (t *Token) CreateOrUpdate() error {
37 | token := &model.UserToken{}
38 | if ok := token.GetOne(model.QueryParam{
39 | Where: []model.WhereParam{
40 | model.WhereParam{
41 | Field: "user_id",
42 | Prepare: t.UserId,
43 | },
44 | },
45 | }); !ok {
46 | return errors.New("get user token detail failed")
47 | }
48 |
49 | token.UserId = t.UserId
50 | token.Token = t.Token
51 | token.Expire = t.Expire
52 |
53 | if token.ID == 0 {
54 | if ok := token.Create(); !ok {
55 | return errors.New("user token create failed")
56 | }
57 | } else {
58 | if ok := token.Update(); !ok {
59 | return errors.New("user token update failed")
60 | }
61 | }
62 |
63 | return nil
64 | }
65 |
66 | func (t *Token) ValidateToken() bool {
67 | if t.UserId == 0 || t.Token == "" {
68 | return false
69 | }
70 | token := &model.UserToken{}
71 | if ok := token.GetOne(model.QueryParam{
72 | Where: []model.WhereParam{
73 | model.WhereParam{
74 | Field: "user_id",
75 | Prepare: t.UserId,
76 | },
77 | model.WhereParam{
78 | Field: "token",
79 | Prepare: t.Token,
80 | },
81 | },
82 | }); !ok {
83 | return false
84 | }
85 | if token.ID == 0 {
86 | return false
87 | }
88 | if token.Expire < int(time.Now().Unix()) {
89 | return false
90 | }
91 | return true
92 | }
93 |
--------------------------------------------------------------------------------
/public/css/chunk-21c9e7bf.a3c25090.css:
--------------------------------------------------------------------------------
1 | .scroll-container[data-v-d88310be]{position:relative;width:100%;height:100%;overflow:hidden}.scroll-container .scroll-wrapper[data-v-d88310be]{position:absolute;width:100%}
--------------------------------------------------------------------------------
/public/css/chunk-e3695f98.b86ea360.css:
--------------------------------------------------------------------------------
1 | .app-login{background-image:url(../img/login_bg.193d505f.jpg);background-position:50%;background-size:cover}.app-login,.app-login .app-login-inner{width:100%;height:100%}.app-login .login-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.app-login .login-container .login-box{margin-top:20vh;width:30vw}.app-login .login-container .login-box .login-title{font-weight:500;text-align:center;font-size:14px;margin-bottom:20px}.app-login .login-cpy{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-top:30px;color:#fff}.app-login .login-cpy a{margin:0 5px;color:#fff}
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/public/favicon.ico
--------------------------------------------------------------------------------
/public/fonts/element-icons.2fad952a.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/public/fonts/element-icons.2fad952a.woff
--------------------------------------------------------------------------------
/public/fonts/element-icons.6f0a7632.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/public/fonts/element-icons.6f0a7632.ttf
--------------------------------------------------------------------------------
/public/img/login_bg.193d505f.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/public/img/login_bg.193d505f.jpg
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 | Syncd - 自动化发布系统
2 |
--------------------------------------------------------------------------------
/public/js/chunk-2d0f0051.d23388af.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0f0051"],{"9b1c":function(n,t,o){"use strict";o.r(t);var e=function(){var n=this,t=n.$createElement,o=n._self._c||t;return o("el-card",[o("p",[n._v("Dashboard Coming Soon...")]),o("p",{staticStyle:{"margin-top":"100px"}},[o("i",{staticClass:"iconfont icon-working",staticStyle:{"font-size":"48px",color:"#409EFF"}})])])},i=[],a=o("2877"),c={},s=Object(a["a"])(c,e,i,!1,null,null,null);s.options.__file="Dashboard.vue";t["default"]=s.exports}}]);
2 | //# sourceMappingURL=chunk-2d0f0051.d23388af.js.map
--------------------------------------------------------------------------------
/public/js/chunk-2d0f0051.d23388af.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack:///./src/view/Dashboard.vue?7af1","webpack:///./src/view/Dashboard.vue"],"names":["render","_vm","this","_h","$createElement","_c","_self","_v","staticStyle","margin-top","staticClass","font-size","color","staticRenderFns","script","component","Object","componentNormalizer","options","__file","__webpack_exports__"],"mappings":"yHAAA,IAAAA,EAAA,WAA0B,IAAAC,EAAAC,KAAaC,EAAAF,EAAAG,eAA0BC,EAAAJ,EAAAK,MAAAD,IAAAF,EAAwB,OAAAE,EAAA,WAAAA,EAAA,KAAAJ,EAAAM,GAAA,8BAAAF,EAAA,KAA0EG,YAAA,CAAaC,aAAA,UAAsB,CAAAJ,EAAA,KAAUK,YAAA,wBAAAF,YAAA,CAAiDG,YAAA,OAAAC,MAAA,kBACjQC,EAAA,eCAAC,EAAA,GAKAC,EAAgBC,OAAAC,EAAA,KAAAD,CAChBF,EACEd,EACAa,GACF,EACA,KACA,KACA,MAIAE,EAAAG,QAAAC,OAAA,gBACeC,EAAA,WAAAL","file":"js/chunk-2d0f0051.d23388af.js","sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('el-card',[_c('p',[_vm._v(\"Dashboard Coming Soon...\")]),_c('p',{staticStyle:{\"margin-top\":\"100px\"}},[_c('i',{staticClass:\"iconfont icon-working\",staticStyle:{\"font-size\":\"48px\",\"color\":\"#409EFF\"}})])])}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./Dashboard.vue?vue&type=template&id=2a5e8ab0&\"\nvar script = {}\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\ncomponent.options.__file = \"Dashboard.vue\"\nexport default component.exports"],"sourceRoot":""}
--------------------------------------------------------------------------------
/public/js/chunk-e3695f98.4489b617.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-e3695f98"],{"0da5":function(e,n,t){"use strict";var o=t("c47e"),r=t.n(o);r.a},"7ded":function(e,n,t){"use strict";t.d(n,"a",function(){return r}),t.d(n,"b",function(){return s}),t.d(n,"c",function(){return i});var o=t("ead3");function r(e){return Object(o["b"])("/login",e)}function s(){return Object(o["a"])("/login/status")}function i(){return Object(o["b"])("/logout")}},c47e:function(e,n,t){},dd1d:function(e,n,t){"use strict";t.r(n);var o=function(){var e=this,n=e.$createElement,t=e._self._c||n;return t("div",{staticClass:"app-login"},[t("div",{staticClass:"app-login-inner",style:{backgroundColor:e.bgColor}},[t("div",{staticClass:"login-container"},[t("el-card",{staticClass:"login-box"},[t("div",{staticClass:"login-title"},[e._v(e._s(e.$t("welcome_to_login_syncd")))]),t("el-form",{ref:"loginFormRef",staticClass:"login-form",attrs:{model:e.loginForm,rules:e.loginRules,size:"medium"},nativeOn:{keyup:function(n){return"button"in n||!e._k(n.keyCode,"enter",13,n.key,"Enter")?e.loginHandler(n):null}}},[t("el-form-item",{attrs:{prop:"username"}},[t("el-input",{attrs:{placeholder:e.$t("username_or_email"),"prefix-icon":"iconfont icon-user",autocomplete:"off"},model:{value:e.loginForm.username,callback:function(n){e.$set(e.loginForm,"username",n)},expression:"loginForm.username"}})],1),t("el-form-item",{attrs:{prop:"password"}},[t("el-input",{attrs:{placeholder:e.$t("password"),"prefix-icon":"iconfont icon-lock",type:"password",autocomplete:"off"},model:{value:e.loginForm.password,callback:function(n){e.$set(e.loginForm,"password",n)},expression:"loginForm.password"}})],1),t("el-form-item",[t("el-button",{staticStyle:{width:"100%"},attrs:{type:"primary"},on:{click:e.loginHandler}},[e._v(e._s(e.$t("login")))])],1)],1)],1)],1),t("div",{staticClass:"login-cpy"},[e._v("\n ©️ "+e._s((new Date).getFullYear())+" "),t("a",{attrs:{href:"https://github.com/dreamans/syncd",target:"_blank"}},[e._v("Syncd")]),e._v(". All Rights Reserved. MIT License.\n ")])])])},r=[],s=(t("cadf"),t("551c"),t("097d"),t("7ded")),i=t("1c06"),a={data:function(){return{loginLoadding:!1,loginForm:{username:"",password:""},loginRules:{username:[{required:!0,message:this.$t("please_input_loginname"),trigger:"blur"}],password:[{required:!0,message:this.$t("please_input_password"),trigger:"blur"},{min:6,max:20,message:this.$t("strlen_between",{min:6,max:20}),trigger:"blur"}]}}},computed:{bgColor:function(){var e=Math.min(Math.abs((new Date).getHours()-12)/10,.8);return"rgba(0, 0, 0, ".concat(e,")")}},methods:{loginHandler:function(){var e=this;this.$refs.loginFormRef.validate(function(n){if(!n)return!1;var t={username:e.loginForm.username,password:e.$root.Md5Sum(e.loginForm.password)};Object(s["a"])(t).then(function(n){e.$root.SetLoginToken(n.token),e.$router.push({name:"dashboard"})}).catch(function(n){n.code&&n.code==i["a"].CODE_ERR_LOGIN_FAILED&&e.$message.error("登录失败, 错误信息: "+n.message)})})}}},l=a,u=(t("0da5"),t("2877")),c=Object(u["a"])(l,o,r,!1,null,null,null);c.options.__file="Login.vue";n["default"]=c.exports}}]);
2 | //# sourceMappingURL=chunk-e3695f98.4489b617.js.map
--------------------------------------------------------------------------------
/render/render.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package render
6 |
7 | import (
8 | "net/http"
9 |
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | const (
14 | CODE_OK = 0
15 | CODE_ERR_SYSTEM = 1000
16 | CODE_ERR_APP = 1001
17 | CODE_ERR_PARAM = 1002
18 | CODE_ERR_DATA_REPEAT = 1003
19 | CODE_ERR_LOGIN_FAILED = 1004
20 | CODE_ERR_NO_LOGIN = 1005
21 | CODE_ERR_NO_PRIV = 1006
22 | CODE_ERR_TASK_ERROR = 1007
23 | CODE_ERR_USER_OR_PASS_WRONG = 1008
24 | CODE_ERR_NO_DATA = 1009
25 | )
26 |
27 | func JSON(c *gin.Context, data interface{}) {
28 | c.JSON(http.StatusOK, gin.H{
29 | "code": CODE_OK,
30 | "message": "success",
31 | "data": data,
32 | })
33 | }
34 |
35 | func CustomerError(c *gin.Context, code int, message string) {
36 | c.JSON(http.StatusOK, gin.H{
37 | "code": code,
38 | "message": message,
39 | })
40 | }
41 |
42 | func RepeatError(c *gin.Context, message string) {
43 | c.JSON(http.StatusOK, gin.H{
44 | "code": CODE_ERR_DATA_REPEAT,
45 | "message": message,
46 | })
47 | }
48 |
49 | func NoDataError(c *gin.Context, message string) {
50 | c.JSON(http.StatusOK, gin.H{
51 | "code": CODE_ERR_NO_DATA,
52 | "message": message,
53 | })
54 | }
55 |
56 | func ParamError(c *gin.Context, message string) {
57 | c.JSON(http.StatusOK, gin.H{
58 | "code": CODE_ERR_PARAM,
59 | "message": message,
60 | })
61 | }
62 |
63 | func AppError(c *gin.Context, message string) {
64 | c.JSON(http.StatusOK, gin.H{
65 | "code": CODE_ERR_APP,
66 | "message": message,
67 | })
68 | }
69 |
70 | func Success(c *gin.Context) {
71 | c.JSON(http.StatusOK, gin.H{
72 | "code": CODE_OK,
73 | "message": "success",
74 | })
75 | }
76 |
--------------------------------------------------------------------------------
/router/common/hook.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package common
6 |
7 | import (
8 | "fmt"
9 |
10 | "github.com/dreamans/syncd"
11 | "github.com/dreamans/syncd/util/gofile"
12 | "github.com/dreamans/syncd/util/command"
13 | "github.com/dreamans/syncd/util/gostring"
14 | "github.com/dreamans/syncd/module/project"
15 | "github.com/dreamans/syncd/module/deploy"
16 | )
17 |
18 | func HookBuild(applyId int) {
19 | apply := &deploy.Apply{
20 | ID: applyId,
21 | }
22 | if err := apply.Detail(); err != nil {
23 | return
24 | }
25 | proj := &project.Project{
26 | ID: apply.ProjectId,
27 | }
28 | if err := proj.Detail(); err != nil {
29 | return
30 | }
31 | if proj.BuildHookScript == "" {
32 | return
33 | }
34 |
35 | build := &deploy.Build{
36 | ApplyId: applyId,
37 | }
38 | if err := build.Detail(); err != nil {
39 | return
40 | }
41 |
42 | var buildStatus int
43 | if build.Status == deploy.BUILD_STATUS_SUCCESS {
44 | buildStatus = 1
45 | }
46 |
47 | s := gostring.JoinStrings(
48 | "#!/bin/bash\n\n",
49 | "#--------- build hook scripts env ---------\n",
50 | fmt.Sprintf("env_apply_id=%d\n", apply.ID),
51 | fmt.Sprintf("env_apply_name='%s'\n", apply.Name),
52 | fmt.Sprintf("env_pack_file='%s'\n", build.Tar),
53 | fmt.Sprintf("env_build_output='%s'\n", build.Output),
54 | fmt.Sprintf("env_build_errmsg='%s'\n", build.Errmsg),
55 | fmt.Sprintf("env_build_status=%d\n", buildStatus),
56 | proj.BuildHookScript,
57 | )
58 | hookCommandTaskRun(s, applyId)
59 | }
60 |
61 | func HookDeploy(applyId int) {
62 | apply := &deploy.Apply{
63 | ID: applyId,
64 | }
65 | if err := apply.Detail(); err != nil {
66 | return
67 | }
68 | proj := &project.Project{
69 | ID: apply.ProjectId,
70 | }
71 | if err := proj.Detail(); err != nil {
72 | return
73 | }
74 | if proj.DeployHookScript == "" {
75 | return
76 | }
77 |
78 | var deployStatus int
79 | if apply.Status == deploy.APPLY_STATUS_SUCCESS {
80 | deployStatus = 1
81 | }
82 |
83 | s := gostring.JoinStrings(
84 | "#!/bin/bash\n\n",
85 | "#--------- deploy hook scripts env ---------\n",
86 | fmt.Sprintf("env_apply_id=%d\n", apply.ID),
87 | fmt.Sprintf("env_apply_name='%s'\n", apply.Name),
88 | fmt.Sprintf("env_deploy_status=%d\n", deployStatus),
89 | proj.DeployHookScript,
90 | )
91 | hookCommandTaskRun(s, applyId)
92 | }
93 |
94 | func hookCommandTaskRun(s string, applyId int) {
95 | scriptFile := gostring.JoinStrings(syncd.App.LocalTmpSpace, "/", gostring.StrRandom(24), ".sh")
96 | if err := gofile.CreateFile(scriptFile, []byte(s), 0744); err != nil {
97 | syncd.App.Logger.Error("hook script file create failed, err[%s], apply_id[%d]", err.Error(), applyId)
98 | return
99 | }
100 | cmds := []string{
101 | fmt.Sprintf("/bin/bash -c %s", scriptFile),
102 | fmt.Sprintf("rm -f %s", scriptFile),
103 | }
104 | task := command.NewTask(cmds, 86400)
105 | task.Run()
106 | if err := task.GetError(); err != nil {
107 | syncd.App.Logger.Error("hook script run failed, err[%s], output[%s], apply_id[%d]", err.Error(), gostring.JsonEncode(task.Result()), applyId)
108 | }
109 | }
--------------------------------------------------------------------------------
/router/common/inspace.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package common
6 |
7 | import (
8 | "github.com/gin-gonic/gin"
9 | "github.com/dreamans/syncd/render"
10 | "github.com/dreamans/syncd/module/project"
11 | )
12 |
13 | func InSpaceCheck(c *gin.Context, spaceId int) bool {
14 | member := &project.Member{
15 | UserId: c.GetInt("user_id"),
16 | SpaceId: spaceId,
17 | }
18 | if in := member.MemberInSpace(); !in {
19 | render.CustomerError(c, render.CODE_ERR_NO_PRIV, "user is not in the project space")
20 | return false
21 | }
22 | return true
23 | }
--------------------------------------------------------------------------------
/router/middleware/api_priv.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package middleware
6 |
7 | import (
8 | "strings"
9 | "github.com/gin-gonic/gin"
10 | "github.com/dreamans/syncd/util/goslice"
11 | "github.com/dreamans/syncd/render"
12 | "github.com/dreamans/syncd/module/user"
13 | reqApi "github.com/dreamans/syncd/router/route/api"
14 | )
15 |
16 | func ApiPriv() gin.HandlerFunc {
17 | return func(c *gin.Context) {
18 | token, _ := c.Cookie("_syd_identity")
19 |
20 | path := strings.TrimSpace(c.Request.URL.Path)
21 | if len(path) < 4 {
22 | respondWithError(c, render.CODE_ERR_NO_PRIV, "request path is too short")
23 | return
24 | }
25 | path = path[4:]
26 |
27 | if path == reqApi.LOGIN {
28 | c.Next()
29 | return
30 | }
31 |
32 | if token == "" {
33 | respondWithError(c, render.CODE_ERR_NO_LOGIN, "no login")
34 | return
35 | }
36 |
37 | login := &user.Login{
38 | Token: token,
39 | }
40 | if err := login.ValidateToken(); err != nil {
41 | respondWithError(c, render.CODE_ERR_NO_LOGIN, err.Error())
42 | return
43 | }
44 |
45 | //priv check
46 | role := &user.Role{
47 | ID: login.RoleId,
48 | }
49 | if err := role.Detail(); err != nil {
50 | respondWithError(c, render.CODE_ERR_APP, err.Error())
51 | return
52 | }
53 |
54 | c.Set("user_id", login.UserId)
55 | c.Set("username", login.Username)
56 | c.Set("email", login.Email)
57 | c.Set("truename", login.Truename)
58 | c.Set("mobile", login.Mobile)
59 | c.Set("role_name", role.Name)
60 | c.Set("privilege", role.Privilege)
61 |
62 | needLoginReq := []string{
63 | reqApi.LOGIN_STATUS,
64 | reqApi.LOGOUT,
65 | reqApi.MY_USER_SETTING,
66 | reqApi.MY_USER_PASSWORD,
67 | }
68 | if goslice.InSliceString(path, needLoginReq) {
69 | c.Next()
70 | return
71 | }
72 |
73 | if pass := user.CheckHavePriv(path, role.Privilege); !pass {
74 | respondWithError(c, render.CODE_ERR_NO_PRIV, "no priv")
75 | return
76 | }
77 |
78 | c.Next()
79 | }
80 | }
81 |
82 | func respondWithError(c *gin.Context, code int, message string) {
83 | render.CustomerError(c, code, message)
84 | c.Abort()
85 | }
86 |
--------------------------------------------------------------------------------
/router/project/member.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package project
6 |
7 | import (
8 | "github.com/gin-gonic/gin"
9 | "github.com/dreamans/syncd/router/common"
10 | "github.com/dreamans/syncd/module/user"
11 | "github.com/dreamans/syncd/module/project"
12 | "github.com/dreamans/syncd/render"
13 | "github.com/dreamans/syncd/util/gostring"
14 | )
15 |
16 | type MemberAddQueryBind struct {
17 | MemberId int `form:"member_id" binding:"required"`
18 | SpaceId int `form:"space_id" binding:"required"`
19 | }
20 |
21 | type MemberListQueryBind struct {
22 | SpaceId int `form:"space_id" binding:"required"`
23 | Offset int `form:"offset"`
24 | Limit int `form:"limit" binding:"required,gte=1,lte=999"`
25 | }
26 |
27 | func MemberRemove(c *gin.Context) {
28 | id := gostring.Str2Int(c.PostForm("id"))
29 | if id == 0 {
30 | render.ParamError(c, "id cannot be empty")
31 | return
32 | }
33 |
34 | member := &project.Member{
35 | ID: id,
36 | }
37 | if err := member.Detail(); err != nil {
38 | render.AppError(c, err.Error())
39 | return
40 | }
41 |
42 | if !common.InSpaceCheck(c, member.SpaceId) {
43 | return
44 | }
45 |
46 | if err := member.Delete(); err != nil {
47 | render.AppError(c, err.Error())
48 | return
49 | }
50 | render.JSON(c, nil)
51 | }
52 |
53 | func MemberList(c *gin.Context) {
54 | var query MemberListQueryBind
55 | if err := c.ShouldBind(&query); err != nil {
56 | render.ParamError(c, err.Error())
57 | return
58 | }
59 |
60 | if !common.InSpaceCheck(c, query.SpaceId) {
61 | return
62 | }
63 |
64 | m := &project.Member{}
65 | memberList, err := m.List(query.SpaceId, query.Offset, query.Limit)
66 | if err != nil {
67 | render.AppError(c, err.Error())
68 | return
69 | }
70 | total, err := m.Total(query.SpaceId)
71 | if err != nil {
72 | render.AppError(c, err.Error())
73 | return
74 | }
75 |
76 | render.JSON(c, gin.H{
77 | "list": memberList,
78 | "total": total,
79 | })
80 | }
81 |
82 | func MemberSearch(c *gin.Context) {
83 | keyword := c.Query("keyword")
84 | u := &user.User{}
85 | list, err := u.List(keyword, 0, 20)
86 | if err != nil {
87 | render.AppError(c, err.Error())
88 | return
89 | }
90 | userList := []map[string]interface{}{}
91 | for _, l := range list {
92 | userList = append(userList, map[string]interface{}{
93 | "id": l.ID,
94 | "username": l.Username,
95 | "email": l.Email,
96 | "status": l.Status,
97 | "role_name": l.RoleName,
98 | })
99 | }
100 | render.JSON(c, userList)
101 | }
102 |
103 | func MemberAdd(c *gin.Context) {
104 | var query MemberAddQueryBind
105 | if err := c.ShouldBind(&query); err != nil {
106 | render.ParamError(c, err.Error())
107 | return
108 | }
109 |
110 | if !common.InSpaceCheck(c, query.SpaceId) {
111 | return
112 | }
113 |
114 | var (
115 | err error
116 | exists bool
117 | )
118 |
119 | u := &user.User{
120 | ID: query.MemberId,
121 | }
122 | exists , err = u.Exists()
123 | if err != nil {
124 | render.AppError(c, err.Error())
125 | return
126 | }
127 | if !exists {
128 | render.NoDataError(c, "user not exists")
129 | return
130 | }
131 |
132 | member := project.Member{
133 | UserId: query.MemberId,
134 | SpaceId: query.SpaceId,
135 | }
136 | exists, err = member.Exists()
137 | if err != nil {
138 | render.AppError(c, err.Error())
139 | return
140 | }
141 | if exists {
142 | render.RepeatError(c, "user have exists in project space")
143 | return
144 | }
145 | if err := member.Create(); err != nil {
146 | render.AppError(c, err.Error())
147 | return
148 | }
149 |
150 | render.Success(c)
151 | }
--------------------------------------------------------------------------------
/router/project/space.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package project
6 |
7 | import (
8 | "github.com/gin-gonic/gin"
9 | "github.com/dreamans/syncd/render"
10 | "github.com/dreamans/syncd/module/project"
11 | "github.com/dreamans/syncd/util/gostring"
12 | )
13 |
14 | type SpaceFormBind struct {
15 | Name string `form:"name" binding:"required"`
16 | Description string `form:"description"`
17 | }
18 |
19 | func SpaceDelete(c *gin.Context) {
20 | id := gostring.Str2Int(c.PostForm("id"))
21 | if id == 0 {
22 | render.ParamError(c, "id cannot be empty")
23 | return
24 | }
25 | member := &project.Member{
26 | UserId: c.GetInt("user_id"),
27 | SpaceId: id,
28 | }
29 | if in := member.MemberInSpace(); !in {
30 | render.CustomerError(c, render.CODE_ERR_NO_PRIV, "user is not in the project space")
31 | return
32 | }
33 | space := &project.Space{
34 | ID: id,
35 | }
36 | if err := space.Delete(); err != nil {
37 | render.AppError(c, err.Error())
38 | return
39 | }
40 | render.JSON(c, nil)
41 | }
42 |
43 | func SpaceDetail(c *gin.Context) {
44 | id := gostring.Str2Int(c.Query("id"))
45 | if id == 0 {
46 | render.ParamError(c, "id cannot be empty")
47 | return
48 | }
49 | member := &project.Member{
50 | UserId: c.GetInt("user_id"),
51 | SpaceId: id,
52 | }
53 | if in := member.MemberInSpace(); !in {
54 | render.CustomerError(c, render.CODE_ERR_NO_PRIV, "user is not in the project space")
55 | return
56 | }
57 | space := &project.Space{
58 | ID: id,
59 | }
60 | if err := space.Detail(); err != nil {
61 | render.AppError(c, err.Error())
62 | return
63 | }
64 | render.JSON(c, space)
65 | }
66 |
67 | func SpaceList(c *gin.Context) {
68 | var query QueryBind
69 | if err := c.ShouldBind(&query); err != nil {
70 | render.ParamError(c, err.Error())
71 | return
72 | }
73 | member := &project.Member{
74 | UserId: c.GetInt("user_id"),
75 | }
76 | spaceIds, err := member.SpaceIdsByUserId()
77 | if err != nil {
78 | render.AppError(c, err.Error())
79 | return
80 | }
81 |
82 | space := &project.Space{}
83 | list, err := space.List(spaceIds, query.Keyword, query.Offset, query.Limit)
84 | if err != nil {
85 | render.AppError(c, err.Error())
86 | return
87 | }
88 |
89 | total, err := space.Total(spaceIds, query.Keyword)
90 | if err != nil {
91 | render.AppError(c, err.Error())
92 | return
93 | }
94 | render.JSON(c, gin.H{
95 | "list": list,
96 | "total": total,
97 | })
98 | }
99 |
100 | func SpaceAdd(c *gin.Context) {
101 | spaceCreateOrUpdate(c, 0)
102 | }
103 |
104 | func SpaceUpdate(c *gin.Context) {
105 | id := gostring.Str2Int(c.PostForm("id"))
106 | if id == 0 {
107 | render.ParamError(c, "id cannot be empty")
108 | return
109 | }
110 | member := &project.Member{
111 | UserId: c.GetInt("user_id"),
112 | SpaceId: id,
113 | }
114 | if in := member.MemberInSpace(); !in {
115 | render.CustomerError(c, render.CODE_ERR_NO_PRIV, "user is not in the project space")
116 | return
117 | }
118 | spaceCreateOrUpdate(c, id)
119 | }
120 |
121 | func spaceCreateOrUpdate(c *gin.Context, id int) {
122 | var spaceForm SpaceFormBind
123 | if err := c.ShouldBind(&spaceForm); err != nil {
124 | render.ParamError(c, err.Error())
125 | return
126 | }
127 | space := &project.Space{
128 | ID: id,
129 | Name: spaceForm.Name,
130 | Description: spaceForm.Description,
131 | }
132 | if err := space.CreateOrUpdate(); err != nil {
133 | render.AppError(c, err.Error())
134 | return
135 | }
136 | if id == 0 {
137 | member := &project.Member{
138 | UserId: c.GetInt("user_id"),
139 | SpaceId: space.ID,
140 | }
141 | if err := member.Create(); err != nil {
142 | render.AppError(c, err.Error())
143 | return
144 | }
145 | }
146 | render.Success(c)
147 | }
148 |
--------------------------------------------------------------------------------
/router/route/api/api.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package api
6 |
7 | const (
8 | LOGIN = "/login"
9 | LOGOUT = "/logout"
10 | LOGIN_STATUS = "/login/status"
11 |
12 | MY_USER_SETTING = "/user/my/setting"
13 | MY_USER_PASSWORD = "/user/my/password"
14 |
15 | SERVER_GROUP_ADD = "/server/group/add"
16 | SERVER_GROUP_LIST = "/server/group/list"
17 | SERVER_GROUP_DELETE = "/server/group/delete"
18 | SERVER_GROUP_DETAIL = "/server/group/detail"
19 | SERVER_GROUP_UPDATE = "/server/group/update"
20 | SERVER_ADD = "/server/add"
21 | SERVER_UPDATE = "/server/update"
22 | SERVER_LIST = "/server/list"
23 | SERVER_DELETE = "/server/delete"
24 | SERVER_DETAIL = "/server/detail"
25 |
26 | USER_ROLE_PRIV_LIST = "/user/role/privlist"
27 | USER_ROLE_ADD = "/user/role/add"
28 | USER_ROLE_UPDATE = "/user/role/update"
29 | USER_ROLE_LIST = "/user/role/list"
30 | USER_ROLE_DETAIL = "/user/role/detail"
31 | USER_ROLE_DELETE = "/user/role/delete"
32 | USER_ADD = "/user/add"
33 | USER_UPDATE = "/user/update"
34 | USER_LIST = "/user/list"
35 | USER_EXISTS = "/user/exists"
36 | USER_DETAIL = "/user/detail"
37 | USER_DELETE = "/user/delete"
38 |
39 | PROJECT_SPACE_ADD = "/project/space/add"
40 | PROJECT_SPACE_UPDATE = "/project/space/update"
41 | PROJECT_SPACE_LIST = "/project/space/list"
42 | PROJECT_SPACE_DETAIL = "/project/space/detail"
43 | PROJECT_SPACE_DELETE = "/project/space/delete"
44 | PROJECT_MEMBER_SEARCH = "/project/member/search"
45 | PROJECT_MEMBER_ADD = "/project/member/add"
46 | PROJECT_MEMBER_LIST = "/project/member/list"
47 | PROJECT_MEMBER_REMOVE = "/project/member/remove"
48 | PROJECT_ADD = "/project/add"
49 | PROJECT_UPDATE = "/project/update"
50 | PROJECT_LIST = "/project/list"
51 | PROJECT_SWITCHSTATUS = "/project/switchstatus"
52 | PROJECT_DETAIL = "/project/detail"
53 | PROJECT_DELETE = "/project/delete"
54 | PROJECT_BUILDSCRIPT = "/project/buildscript"
55 | PROJECT_HOOKSCRIPT = "/project/hookscript"
56 |
57 | DEPLOY_APPLY_PROJECT_DETAIL = "/deploy/apply/project/detail"
58 | DEPLOY_APPLY_SUBMIT = "/deploy/apply/submit"
59 | DEPLOY_APPLY_PROJECT_ALL = "/deploy/apply/project/all"
60 | DEPLOY_APPLY_LIST = "/deploy/apply/list"
61 | DEPLOY_APPLY_DETAIL = "/deploy/apply/detail"
62 | DEPLOY_APPLY_AUDIT = "/deploy/apply/audit"
63 | DEPLOY_APPLY_UPDATE = "/deploy/apply/update"
64 | DEPLOY_APPLY_DROP = "/deploy/apply/drop"
65 | DEPLOY_APPLY_ROLLBACK = "/deploy/apply/rollbacklist"
66 | DEPLOY_BUILD_START = "/deploy/build/start"
67 | DEPLOY_BUILD_STATUS = "/deploy/build/status"
68 | DEPLOY_BUILD_STOP = "/deploy/build/stop"
69 | DEPLOY_DEPLOY_START = "/deploy/deploy/start"
70 | DEPLOY_DEPLOY_STATUS = "/deploy/deploy/status"
71 | DEPLOY_DEPLOY_STOP = "/deploy/deploy/stop"
72 | DEPLOY_DEPLOY_ROLLBACK = "/deploy/deploy/rollback"
73 | )
74 |
--------------------------------------------------------------------------------
/router/server/group.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package server
6 |
7 | import (
8 | "github.com/gin-gonic/gin"
9 | "github.com/dreamans/syncd/render"
10 | "github.com/dreamans/syncd/module/server"
11 | "github.com/dreamans/syncd/util/gostring"
12 | )
13 |
14 | type GroupForm struct {
15 | ID int `form:"id"`
16 | Name string `form:"name" binding:"required"`
17 | }
18 |
19 | func GroupAdd(c *gin.Context) {
20 | var groupForm GroupForm
21 | if err := c.ShouldBind(&groupForm); err != nil {
22 | render.ParamError(c, err.Error())
23 | return
24 | }
25 | group := &server.Group{
26 | Name: groupForm.Name,
27 | }
28 | if err := group.Create(); err != nil {
29 | render.AppError(c, err.Error())
30 | return
31 | }
32 | render.Success(c)
33 | }
34 |
35 | func GroupList(c *gin.Context) {
36 | var query QueryBind
37 | if err := c.ShouldBind(&query); err != nil {
38 | render.ParamError(c, err.Error())
39 | return
40 | }
41 | group := &server.Group{}
42 | list, err := group.List(query.Keyword, query.Offset, query.Limit)
43 | if err != nil {
44 | render.AppError(c, err.Error())
45 | return
46 | }
47 | total, err := group.Total(query.Keyword)
48 | if err != nil {
49 | render.AppError(c, err.Error())
50 | return
51 | }
52 | render.JSON(c, gin.H{
53 | "list": list,
54 | "total": total,
55 | })
56 | }
57 |
58 | func GroupDelete(c *gin.Context) {
59 | id := gostring.Str2Int(c.PostForm("id"))
60 | if id == 0 {
61 | render.ParamError(c, "id cannot be empty")
62 | return
63 | }
64 | group := &server.Group{
65 | ID: id,
66 | }
67 | if err := group.Delete(); err != nil {
68 | render.AppError(c, err.Error())
69 | return
70 | }
71 | render.JSON(c, nil)
72 | }
73 |
74 | func GroupDetail(c *gin.Context) {
75 | id := gostring.Str2Int(c.Query("id"))
76 | if id == 0 {
77 | render.ParamError(c, "id cannot be empty")
78 | return
79 | }
80 | group := &server.Group{
81 | ID: id,
82 | }
83 | if err := group.Detail(); err != nil {
84 | render.AppError(c, err.Error())
85 | return
86 | }
87 | render.JSON(c, group)
88 | }
89 |
90 | func GroupUpdate(c *gin.Context) {
91 | var groupForm GroupForm
92 | if err := c.ShouldBind(&groupForm); err != nil {
93 | render.ParamError(c, err.Error())
94 | return
95 | }
96 | if groupForm.ID == 0 {
97 | render.ParamError(c, "id cannot be empty")
98 | return
99 | }
100 | group := &server.Group{
101 | ID: groupForm.ID,
102 | Name: groupForm.Name,
103 | }
104 | if err := group.Update(); err != nil {
105 | render.AppError(c, err.Error())
106 | return
107 | }
108 | render.Success(c)
109 | }
110 |
--------------------------------------------------------------------------------
/router/server/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package server
6 |
7 | import (
8 | "github.com/gin-gonic/gin"
9 | "github.com/dreamans/syncd/render"
10 | "github.com/dreamans/syncd/module/server"
11 | "github.com/dreamans/syncd/util/gostring"
12 | )
13 |
14 | type QueryBind struct {
15 | Keyword string `form:"keyword"`
16 | Offset int `form:"offset"`
17 | Limit int `form:"limit" binding:"required,gte=1,lte=999"`
18 | }
19 |
20 | type ServerForm struct {
21 | GroupId int `form:"group_id" binding:"required"`
22 | Name string `form:"name" binding:"required"`
23 | Ip string `form:"ip" binding:"required"`
24 | SSHPort int `form:"ssh_port" binding:"required,gte=1,lte=65535"`
25 | }
26 |
27 | func ServerAdd(c *gin.Context) {
28 | serverCreateOrUpdate(c, 0)
29 | }
30 |
31 | func ServerUpdate(c *gin.Context) {
32 | id := gostring.Str2Int(c.PostForm("id"))
33 | if id == 0 {
34 | render.ParamError(c, "id cannot be empty")
35 | return
36 | }
37 | serverCreateOrUpdate(c, id)
38 | }
39 |
40 | func serverCreateOrUpdate(c *gin.Context, id int) {
41 | var serverForm ServerForm
42 | if err := c.ShouldBind(&serverForm); err != nil {
43 | render.ParamError(c, err.Error())
44 | return
45 | }
46 | server := &server.Server{
47 | ID: id,
48 | GroupId: serverForm.GroupId,
49 | Name: serverForm.Name,
50 | Ip: serverForm.Ip,
51 | SSHPort: serverForm.SSHPort,
52 | }
53 | if err := server.CreateOrUpdate(); err != nil {
54 | render.AppError(c, err.Error())
55 | return
56 | }
57 | render.Success(c)
58 | }
59 |
60 | func ServerList(c *gin.Context) {
61 | var query QueryBind
62 | if err := c.ShouldBind(&query); err != nil {
63 | render.ParamError(c, err.Error())
64 | return
65 | }
66 | ser := &server.Server{}
67 | list, err := ser.List(query.Keyword, query.Offset, query.Limit)
68 | if err != nil {
69 | render.AppError(c, err.Error())
70 | return
71 | }
72 |
73 | total, err := ser.Total(query.Keyword)
74 | if err != nil {
75 | render.AppError(c, err.Error())
76 | return
77 | }
78 | render.JSON(c, gin.H{
79 | "list": list,
80 | "total": total,
81 | })
82 | }
83 |
84 | func ServerDelete(c *gin.Context) {
85 | id := gostring.Str2Int(c.PostForm("id"))
86 | if id == 0 {
87 | render.ParamError(c, "id cannot be empty")
88 | return
89 | }
90 | server := &server.Server{
91 | ID: id,
92 | }
93 | if err := server.Delete(); err != nil {
94 | render.AppError(c, err.Error())
95 | return
96 | }
97 | render.JSON(c, nil)
98 | }
99 |
100 | func ServerDetail(c *gin.Context) {
101 | id := gostring.Str2Int(c.Query("id"))
102 | if id == 0 {
103 | render.ParamError(c, "id cannot be empty")
104 | return
105 | }
106 | ser := &server.Server{
107 | ID: id,
108 | }
109 | if err := ser.Detail(); err != nil {
110 | render.AppError(c, err.Error())
111 | return
112 | }
113 |
114 | group := &server.Group{
115 | ID: ser.GroupId,
116 | }
117 | if err := group.Detail(); err == nil {
118 | ser.GroupName = group.Name
119 | }
120 |
121 | render.JSON(c, ser)
122 | }
123 |
--------------------------------------------------------------------------------
/router/user/login.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package user
6 |
7 | import (
8 | "github.com/gin-gonic/gin"
9 | "github.com/dreamans/syncd/render"
10 | "github.com/dreamans/syncd/module/user"
11 | "github.com/dreamans/syncd/util/gois"
12 |
13 | )
14 |
15 | type LoginBind struct {
16 | Username string `form:"username" binding:"required"`
17 | Password string `form:"password" binding:"required"`
18 | }
19 |
20 | func Logout(c *gin.Context) {
21 | login := &user.Login{
22 | UserId: c.GetInt("user_id"),
23 | }
24 | if err := login.Logout(); err != nil {
25 | render.AppError(c, err.Error())
26 | return
27 | }
28 | render.Success(c)
29 | }
30 |
31 | func Login(c *gin.Context) {
32 | var form LoginBind
33 | if err := c.ShouldBind(&form); err != nil {
34 | render.ParamError(c, err.Error())
35 | return
36 | }
37 |
38 | login := &user.Login{
39 | Password: form.Password,
40 | }
41 | if gois.IsEmail(form.Username) {
42 | login.Email = form.Username
43 | } else {
44 | login.Username = form.Username
45 | }
46 |
47 | if err := login.Login(); err != nil {
48 | render.CustomerError(c, render.CODE_ERR_LOGIN_FAILED , err.Error())
49 | return
50 | }
51 |
52 | userInfo := map[string]interface{}{
53 | "token": login.Token,
54 | }
55 |
56 | render.JSON(c, userInfo)
57 | }
58 |
59 | func LoginStatus(c *gin.Context) {
60 | privilege, _ := c.Get("privilege")
61 | render.JSON(c, gin.H{
62 | "is_login": 1,
63 | "user_id": c.GetInt("user_id"),
64 | "username": c.GetString("username"),
65 | "email": c.GetString("email"),
66 | "truename": c.GetString("truename"),
67 | "mobile": c.GetString("mobile"),
68 | "role_name": c.GetString("role_name"),
69 | "privilege": privilege,
70 | })
71 | }
72 |
--------------------------------------------------------------------------------
/router/user/my.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package user
6 |
7 | import (
8 | "github.com/gin-gonic/gin"
9 | "github.com/dreamans/syncd/render"
10 | "github.com/dreamans/syncd/module/user"
11 | "github.com/dreamans/syncd/util/gostring"
12 | )
13 |
14 | type UserSettingForm struct {
15 | Truename string `form:"truename"`
16 | Mobile string `form:"mobile"`
17 | }
18 |
19 | type UserPasswordForm struct {
20 | Password string `form:"password"`
21 | NewPassword string `form:"new_password"`
22 | }
23 |
24 | func MyUserSetting(c *gin.Context) {
25 | var form UserSettingForm
26 | if err := c.ShouldBind(&form); err != nil {
27 | render.ParamError(c, err.Error())
28 | return
29 | }
30 |
31 | u := &user.User{
32 | ID: c.GetInt("user_id"),
33 | Truename: form.Truename,
34 | Mobile: form.Mobile,
35 | }
36 | if err := u.UserSettingUpdate(); err != nil {
37 | render.AppError(c, err.Error())
38 | return
39 | }
40 | render.Success(c)
41 | }
42 |
43 | func MyUserPassword(c *gin.Context) {
44 | var form UserPasswordForm
45 | if err := c.ShouldBind(&form); err != nil {
46 | render.ParamError(c, err.Error())
47 | return
48 | }
49 | user := &user.User{
50 | ID: c.GetInt("user_id"),
51 | }
52 | if err := user.Detail(); err != nil {
53 | render.AppError(c, err.Error())
54 | return
55 | }
56 |
57 | if user.Password != gostring.StrMd5(gostring.JoinStrings(form.Password, user.Salt)) {
58 | render.CustomerError(c, render.CODE_ERR_USER_OR_PASS_WRONG, "current password wrong")
59 | return
60 | }
61 |
62 | user.Password = form.NewPassword
63 | if err := user.UpdatePassword(); err != nil {
64 | render.AppError(c, err.Error())
65 | return
66 | }
67 |
68 | render.Success(c)
69 | }
70 |
--------------------------------------------------------------------------------
/router/user/role.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package user
6 |
7 | import (
8 | "github.com/gin-gonic/gin"
9 | "github.com/dreamans/syncd/render"
10 | "github.com/dreamans/syncd/module/user"
11 | "github.com/dreamans/syncd/util/gostring"
12 | "github.com/dreamans/syncd/util/goslice"
13 | )
14 |
15 | type RoleFormBind struct {
16 | ID int `form:"id"`
17 | Name string `form:"name" binding:"required"`
18 | Privilege []int `form:"privilege"`
19 | }
20 |
21 | func RolePrivList(c *gin.Context) {
22 | render.JSON(c, user.PrivList)
23 | }
24 |
25 | func RoleDelete(c *gin.Context) {
26 | id := gostring.Str2Int(c.PostForm("id"))
27 | if id == 0 {
28 | render.ParamError(c, "id cannot be empty")
29 | return
30 | }
31 | role := &user.Role{
32 | ID: id,
33 | }
34 | if err := role.Delete(); err != nil {
35 | render.AppError(c, err.Error())
36 | return
37 | }
38 | render.JSON(c, nil)
39 | }
40 |
41 | func RoleDetail(c *gin.Context) {
42 | id := gostring.Str2Int(c.Query("id"))
43 | if id == 0 {
44 | render.ParamError(c, "id cannot be empty")
45 | return
46 | }
47 | role := &user.Role{
48 | ID: id,
49 | }
50 | if err := role.Detail(); err != nil {
51 | render.AppError(c, err.Error())
52 | return
53 | }
54 | render.JSON(c, role)
55 | }
56 |
57 | func RoleList(c *gin.Context) {
58 | var query QueryBind
59 | if err := c.ShouldBind(&query); err != nil {
60 | render.ParamError(c, err.Error())
61 | return
62 | }
63 | role := &user.Role{}
64 | list, err := role.List(query.Keyword, query.Offset, query.Limit)
65 | if err != nil {
66 | render.AppError(c, err.Error())
67 | return
68 | }
69 | total, err := role.Total(query.Keyword)
70 | if err != nil {
71 | render.AppError(c, err.Error())
72 | return
73 | }
74 |
75 | var roleList []map[string]interface{}
76 | for _, l := range list {
77 | roleList = append(roleList, map[string]interface{}{
78 | "id": l.ID,
79 | "name": l.Name,
80 | "ctime": l.Ctime,
81 | })
82 | }
83 |
84 | render.JSON(c, gin.H{
85 | "list": roleList,
86 | "total": total,
87 | })
88 | }
89 |
90 | func RoleAdd(c *gin.Context) {
91 | roleCreateOrUpdate(c, 0)
92 | }
93 |
94 | func RoleUpdate(c *gin.Context) {
95 | id := gostring.Str2Int(c.PostForm("id"))
96 | if id == 0 {
97 | render.ParamError(c, "id cannot be empty")
98 | return
99 | }
100 | roleCreateOrUpdate(c, id)
101 | }
102 |
103 | func roleCreateOrUpdate(c *gin.Context, id int) {
104 | var roleForm RoleFormBind
105 | if err := c.ShouldBind(&roleForm); err != nil {
106 | render.ParamError(c, err.Error())
107 | return
108 | }
109 | role := user.Role{
110 | ID: roleForm.ID,
111 | Name: roleForm.Name,
112 | Privilege: goslice.FilterSliceInt(roleForm.Privilege),
113 | }
114 | if err := role.CreateOrUpdate(); err != nil {
115 | render.AppError(c, err.Error())
116 | return
117 | }
118 | render.Success(c)
119 | }
120 |
--------------------------------------------------------------------------------
/script/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | syncd_remote_repo=https://github.com/dreamans/syncd.git
4 | syncd_repo_path=$HOME/.syncd-repo
5 | syncd_install_path=$( cd `dirname $0`; pwd )/syncd-deploy
6 |
7 | checkCommand() {
8 | type $1 > /dev/null 2>&1
9 | if [ $? -ne 0 ]; then
10 | echo "error: $1 must be installed"
11 | echo "install exit"
12 | exit 1
13 | fi
14 | }
15 |
16 | checkCommand "go"
17 | checkCommand "git"
18 | checkCommand "make"
19 |
20 | if [ -d ${syncd_install_path} ];then
21 | syncd_install_path=${syncd_install_path}-$( date +%Y%m%d%H%M%S )
22 | fi
23 |
24 | rm -fr ${syncd_repo_path}
25 | git clone ${syncd_remote_repo} ${syncd_repo_path}
26 |
27 | cd ${syncd_repo_path}
28 | make
29 |
30 | rm -fr ${syncd_install_path}
31 | cp -r ${syncd_repo_path}/output ${syncd_install_path}
32 |
33 | rm -fr ${syncd_repo_path}
34 |
35 | cat << EOF
36 |
37 | Installing Syncd Path: ${syncd_install_path}
38 | Install complete.
39 |
40 | EOF
--------------------------------------------------------------------------------
/sendmail.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package syncd
6 |
7 | import (
8 | "strings"
9 |
10 | "gopkg.in/gomail.v2"
11 | )
12 |
13 | type SendMail struct {
14 | Enable int
15 | Smtp string
16 | Port int
17 | User string
18 | Pass string
19 | dialer *gomail.Dialer
20 | }
21 |
22 | func NewSendMail(mail *SendMail) *SendMail {
23 | mail.dialer = gomail.NewPlainDialer(mail.Smtp, mail.Port, mail.User, mail.Pass)
24 | return mail
25 | }
26 |
27 | func (mail *SendMail) send(msg *SendMailMessage) error {
28 | if mail.Enable == 0 {
29 | return nil
30 | }
31 | msg.mail = mail
32 | m := msg.NewMessage()
33 | if err := mail.dialer.DialAndSend(m); err != nil {
34 | return err
35 | }
36 | return nil
37 | }
38 |
39 | func (mail *SendMail) Send(msg *SendMailMessage) {
40 | if err := mail.send(msg); err != nil {
41 | App.Logger.Error(
42 | "send mail failed, to[%s], cc[%s], subject[%s]",
43 | strings.Join(msg.To, ","),
44 | strings.Join(msg.Cc, ","),
45 | msg.Subject,
46 | )
47 | }
48 | }
49 |
50 | func (mail *SendMail) AsyncSend(msg *SendMailMessage) {
51 | go mail.Send(msg)
52 | }
53 |
54 | type SendMailMessage struct {
55 | From string
56 | To []string
57 | Cc []string
58 | Subject string
59 | Body string
60 | Attach string
61 | mail *SendMail
62 | }
63 |
64 | func (m *SendMailMessage) NewMessage() *gomail.Message {
65 | mailMsg := gomail.NewMessage()
66 | if m.From == "" {
67 | mailMsg.SetHeader("From", m.mail.User)
68 | } else {
69 | mailMsg.SetHeader("From", m.From)
70 | }
71 | mailMsg.SetHeader("To", m.To...)
72 | if len(m.Cc) > 0 {
73 | mailMsg.SetHeader("Cc", m.Cc...)
74 | }
75 | mailMsg.SetHeader("Subject", m.Subject)
76 | mailMsg.SetBody("text/html", m.Body)
77 | if m.Attach != "" {
78 | mailMsg.Attach(m.Attach)
79 | }
80 | return mailMsg
81 | }
--------------------------------------------------------------------------------
/syncd.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package syncd
6 |
7 | import (
8 | "errors"
9 | "encoding/base64"
10 | "fmt"
11 | "os"
12 | "io"
13 |
14 | "github.com/gin-gonic/gin"
15 | "github.com/dreamans/syncd/util/golog"
16 | "github.com/dreamans/syncd/util/gopath"
17 | )
18 |
19 | var (
20 | App *syncd
21 | )
22 |
23 | const (
24 | Version = "v2.0.0"
25 | )
26 |
27 | func init() {
28 | App = newSyncd()
29 | }
30 |
31 | type syncd struct {
32 | Gin *gin.Engine
33 | DB *DB
34 | Logger *golog.Logger
35 | Mail *SendMail
36 | LocalSpace string
37 | LocalTmpSpace string
38 | LocalTarSpace string
39 | RemoteSpace string
40 | CipherKey []byte
41 | AppHost string
42 | FeServeEnable int
43 | config *Config
44 | }
45 |
46 | func newSyncd() *syncd {
47 | return &syncd{
48 | Gin: gin.New(),
49 | }
50 | }
51 |
52 | func (s *syncd) Init(cfg *Config) error {
53 | s.config = cfg
54 |
55 | if err := s.registerOrm(); err != nil {
56 | return err
57 | }
58 | s.registerMail()
59 | s.registerLog()
60 |
61 | if err := s.initEnv(); err != nil {
62 | return err
63 | }
64 | return nil
65 | }
66 |
67 | func (s *syncd) Start() error {
68 | return s.Gin.Run(s.config.Serve.Addr)
69 | }
70 |
71 | func (s *syncd) registerOrm() error {
72 | s.DB = NewDatabase(s.config.Db)
73 | return s.DB.Open()
74 | }
75 |
76 | func (s *syncd) registerLog() {
77 | var loggerHandler io.Writer
78 | switch s.config.Log.Path {
79 | case "stdout":
80 | loggerHandler = os.Stdout
81 | case "stderr":
82 | loggerHandler = os.Stderr
83 | case "":
84 | loggerHandler = os.Stdout
85 | default:
86 | loggerHandler = golog.NewFileHandler(s.config.Log.Path)
87 | }
88 | s.Logger = golog.New(loggerHandler)
89 | }
90 |
91 | func (s *syncd) registerMail() {
92 | sendmail := &SendMail{
93 | Enable: s.config.Mail.Enable,
94 | Smtp: s.config.Mail.Smtp,
95 | Port: s.config.Mail.Port,
96 | User: s.config.Mail.User,
97 | Pass: s.config.Mail.Pass,
98 | }
99 | s.Mail = NewSendMail(sendmail)
100 | }
101 |
102 | func (s *syncd) initEnv() error {
103 | s.AppHost = s.config.Syncd.AppHost
104 | s.FeServeEnable = s.config.Serve.FeServeEnable
105 | s.LocalSpace = s.config.Syncd.LocalSpace
106 | s.LocalTmpSpace = s.LocalSpace + "/tmp"
107 | s.LocalTarSpace = s.LocalSpace + "/tar"
108 |
109 | if err := gopath.CreatePath(s.LocalSpace); err != nil {
110 | return err
111 | }
112 | if err := gopath.CreatePath(s.LocalTmpSpace); err != nil {
113 | return err
114 | }
115 | if err := gopath.CreatePath(s.LocalTarSpace); err != nil {
116 | return err
117 | }
118 |
119 | s.RemoteSpace = s.config.Syncd.RemoteSpace
120 | if s.config.Syncd.Cipher == "" {
121 | return errors.New("syncd config 'Cipher' not setting")
122 | }
123 | dec, err := base64.StdEncoding.DecodeString(s.config.Syncd.Cipher)
124 | if err != nil {
125 | return errors.New(fmt.Sprintf("decode Cipher failed, %s", err.Error()))
126 | }
127 | s.CipherKey = dec
128 |
129 | return nil
130 | }
131 |
--------------------------------------------------------------------------------
/syncd.ini:
--------------------------------------------------------------------------------
1 | [syncd]
2 | ; 项目访问域名, 结尾不要加 `/`
3 | app_host = http://localhost:8878
4 |
5 | ; 工作空间
6 | local_space = /tmp/syncd_data
7 |
8 | ; 远端机器工作空间
9 | remote_space = ~/.syncd
10 |
11 | ; AES加密/解密使用的私钥
12 | ; 秘钥需要进行base64编码
13 | ; 16 => AES-128, 24 => AES-192, 32 => AES-256
14 | cipher_key = pG1L62EM0cPIIOwusQsbcV8Cs6j/M1RxLoXIZylWUC4=
15 |
16 | [serve]
17 | ; HTTP服务监听的端口
18 | addr = :8878
19 |
20 | ; 是否开启前端资源服务
21 | ; 开启后Syncd前端资源将不再依赖nginx等web服务
22 | ; 1 - 开启
23 | ; 0 - 关闭
24 | fe_serve_enable = 1
25 |
26 | ; 读超时时间设置, 单位秒
27 | read_timeout = 300
28 |
29 | ; 写超时时间设置, 单位秒
30 | write_timeout = 300
31 |
32 | ; 空闲连接超时设置, 单位秒
33 | idle_timeout = 300
34 |
35 | [database]
36 | ; 数据库连接信息
37 | ; 必须是utf8mb4编码
38 | ;unix =
39 | ;max_idle_conns = 100
40 | ;max_open_conns = 200
41 | ;conn_max_life_time = 500
42 | host = 127.0.0.1
43 | port = 3306
44 | user = syncd
45 | password = syncd
46 | dbname = syncd
47 |
48 | [log]
49 | ; 日志输出路径
50 | ; path = stdout - 打印到标准输出
51 | ; path = /path/file - 输出到文件
52 | path = stdout
53 |
54 | [mail]
55 | ; 是否开启邮件发送功能
56 | ; 0 - 关闭
57 | ; 1 - 开启
58 | enable = 0
59 |
60 | ; 邮件smtp配置
61 | smtp_host = smtp.exmail.qq.com
62 | smtp_port = 465
63 | smtp_user =
64 | smtp_pass =
65 |
--------------------------------------------------------------------------------
/util/command/command.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package command
6 |
7 | import (
8 | "bytes"
9 | "os/exec"
10 | "time"
11 | "syscall"
12 | "errors"
13 | "fmt"
14 | "strings"
15 | )
16 |
17 | const (
18 | DEFAULT_RUM_TIMEOUT = 3600
19 | )
20 |
21 | type Command struct {
22 | Cmd string
23 | Timeout time.Duration
24 | TerminateChan chan int
25 | Setpgid bool
26 | command *exec.Cmd
27 | stdout bytes.Buffer
28 | stderr bytes.Buffer
29 | }
30 |
31 | func NewCmd(c *Command) (*Command, error) {
32 | if c.Timeout == 0 * time.Second {
33 | c.Timeout = DEFAULT_RUM_TIMEOUT * time.Second
34 | }
35 | if c.TerminateChan == nil {
36 | c.TerminateChan = make(chan int)
37 | }
38 | cmd := exec.Command("/bin/bash", "-c", c.Cmd)
39 | if c.Setpgid {
40 | cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
41 | }
42 | cmd.Stderr = &c.stderr
43 | cmd.Stdout = &c.stdout
44 | c.command = cmd
45 |
46 | return c, nil
47 | }
48 |
49 | func (c *Command) Run() error {
50 | if err := c.command.Start(); err != nil {
51 | return err
52 | }
53 |
54 | errChan := make(chan error)
55 | go func(){
56 | errChan <- c.command.Wait()
57 | defer close(errChan)
58 | }()
59 |
60 | var err error
61 | select {
62 | case err = <-errChan:
63 | case <-time.After(c.Timeout):
64 | err = c.terminate()
65 | if err == nil {
66 | err = errors.New(fmt.Sprintf("cmd run timeout, cmd [%s], time[%v]", c.Cmd, c.Timeout))
67 | }
68 | case <-c.TerminateChan:
69 | err = c.terminate()
70 | if err == nil {
71 | err = errors.New(fmt.Sprintf("cmd is terminated, cmd [%s]", c.Cmd))
72 | }
73 | }
74 | return err
75 | }
76 |
77 | func (c *Command) Stderr() string {
78 | return strings.TrimSpace(string(c.stderr.Bytes()))
79 | }
80 |
81 | func (c *Command) Stdout() string {
82 | return strings.TrimSpace(string(c.stdout.Bytes()))
83 | }
84 |
85 | func (c *Command) terminate() error {
86 | if c.Setpgid {
87 | return syscall.Kill(-c.command.Process.Pid, syscall.SIGKILL)
88 | } else {
89 | return syscall.Kill(c.command.Process.Pid, syscall.SIGKILL)
90 | }
91 | }
--------------------------------------------------------------------------------
/util/command/command_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package command
6 |
7 | import (
8 | "testing"
9 | "time"
10 | "strings"
11 | )
12 |
13 | func TestCmdRun(t *testing.T) {
14 | c := &Command{
15 | Cmd: "echo 'syncd'",
16 | Timeout: time.Second * 60,
17 | }
18 | var err error
19 | if c, err = NewCmd(c); err != nil {
20 | t.Error(err)
21 | }
22 | if err = c.Run(); err != nil {
23 | t.Error(err)
24 | }
25 | output := c.Stdout()
26 | if output != "syncd" {
27 | t.Errorf("cmd `echo syncd` output '%s' not eq 'syncd'", output)
28 | }
29 | }
30 |
31 | func TestCmdTimeout(t *testing.T) {
32 | c := &Command{
33 | Cmd: "sleep 2",
34 | Timeout: time.Second * 1,
35 | }
36 | var err error
37 | if c, err = NewCmd(c); err != nil {
38 | t.Error(err)
39 | }
40 | err = c.Run()
41 | if err == nil {
42 | t.Error("cmd should run timeout and output errmsg, but err is nil")
43 | }
44 | if strings.Index(err.Error(), "cmd run timeout") == -1 {
45 | t.Errorf("cmd run timeout output '%s' prefix not eq 'cmd run timeout'", err.Error())
46 | }
47 | }
48 |
49 | func TestCmdTerminate(t *testing.T) {
50 | tChan := make(chan int)
51 | c := &Command{
52 | Cmd: "sleep 5",
53 | TerminateChan: tChan,
54 | }
55 | var err error
56 | if c, err = NewCmd(c); err != nil {
57 | t.Error(err)
58 | }
59 | go func() {
60 | err = c.Run()
61 | if err == nil {
62 | t.Error("cmd should be terminated and output errmsg, but err is nil")
63 | }
64 | if strings.Index(err.Error(), "cmd is terminated") == -1 {
65 | t.Errorf("cmd is terminated output '%s' prefix not eq 'cmd is terminated'", err.Error())
66 | }
67 | }()
68 |
69 | tChan<- 1
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/util/command/task.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package command
6 |
7 | import (
8 | "errors"
9 | "time"
10 | )
11 |
12 | type Task struct {
13 | Commands []string
14 | done bool
15 | timeout int
16 | termChan chan int
17 | err error
18 | result []*TaskResult
19 | }
20 |
21 | type TaskResult struct {
22 | Cmd string `json:"cmd"`
23 | Stdout string `json:"stdout"`
24 | Stderr string `json:"stderr"`
25 | Success bool `json:"success"`
26 | }
27 |
28 | func NewTask(cmds []string, timeout int) *Task {
29 | return &Task{
30 | Commands: cmds,
31 | termChan: make(chan int),
32 | timeout: timeout,
33 | }
34 | }
35 |
36 | func (t *Task) Run() {
37 | for _, cmd := range t.Commands {
38 | result, err := t.next(cmd)
39 | t.result = append(t.result, result)
40 | if err != nil {
41 | t.err = errors.New("task run failed, " + err.Error())
42 | break
43 | }
44 | }
45 | t.done = true
46 | }
47 |
48 | func (t *Task) GetError() error {
49 | return t.err
50 | }
51 |
52 | func (t *Task) Result() []*TaskResult {
53 | return t.result
54 | }
55 |
56 | func (t *Task) next(cmd string) (*TaskResult, error) {
57 | result := &TaskResult{
58 | Cmd: cmd,
59 | }
60 | command := &Command{
61 | Cmd: cmd,
62 | Timeout: time.Second * time.Duration(t.timeout),
63 | TerminateChan: t.termChan,
64 | }
65 | var err error
66 | command, err = NewCmd(command)
67 | if err != nil {
68 | return result, err
69 | }
70 | if err := command.Run(); err != nil {
71 | result.Stderr = command.Stderr()
72 | return result, err
73 | }
74 | result.Stdout = command.Stdout()
75 | result.Success = true
76 | return result, nil
77 | }
78 |
79 | func (t *Task) Terminate() {
80 | if !t.done {
81 | t.termChan <- 1
82 | }
83 | }
--------------------------------------------------------------------------------
/util/command/task_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package command
6 |
7 | import (
8 | "testing"
9 | )
10 |
11 | func TestTaskRun(t *testing.T) {
12 | cmds := []string{
13 | "echo 'syncd'",
14 | "whoami",
15 | "date",
16 | }
17 | task := TaskNew(cmds, 10)
18 | task.Run()
19 | if err := task.GetError(); err != nil {
20 | t.Errorf("cmd task running error: %s", err.Error())
21 | }
22 | }
--------------------------------------------------------------------------------
/util/goaes/aes.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 | // license at https://github.com/snail007/goproxy/blob/master/LICENSE
5 |
6 | package goaes
7 |
8 | import (
9 | "bytes"
10 | "crypto/aes"
11 | "crypto/cipher"
12 | "crypto/rand"
13 | "errors"
14 | "io"
15 | )
16 |
17 | func Encrypt(key []byte, text []byte) ([]byte, error) {
18 | block, err := aes.NewCipher(key)
19 | if err != nil {
20 | return nil, err
21 | }
22 |
23 | msg := pad(text)
24 | ciphertext := make([]byte, aes.BlockSize+len(msg))
25 | iv := ciphertext[:aes.BlockSize]
26 | if _, err := io.ReadFull(rand.Reader, iv); err != nil {
27 | return nil, err
28 | }
29 |
30 | cfb := cipher.NewCFBEncrypter(block, iv)
31 | cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(msg))
32 |
33 | return ciphertext, nil
34 | }
35 |
36 | func Decrypt(key []byte, text []byte) ([]byte, error) {
37 | block, err := aes.NewCipher(key)
38 | if err != nil {
39 | return nil, err
40 | }
41 | if (len(text) % aes.BlockSize) != 0 {
42 | return nil, errors.New("blocksize must be multipe of decoded message length")
43 | }
44 | iv := text[:aes.BlockSize]
45 | msg := text[aes.BlockSize:]
46 |
47 | cfb := cipher.NewCFBDecrypter(block, iv)
48 | cfb.XORKeyStream(msg, msg)
49 |
50 | unpadMsg, err := unpad(msg)
51 | if err != nil {
52 | return nil, err
53 | }
54 |
55 | return unpadMsg, nil
56 | }
57 |
58 | func pad(src []byte) []byte {
59 | padding := aes.BlockSize - len(src)%aes.BlockSize
60 | padtext := bytes.Repeat([]byte{byte(padding)}, padding)
61 | return append(src, padtext...)
62 | }
63 |
64 | func unpad(src []byte) ([]byte, error) {
65 | length := len(src)
66 | unpadding := int(src[length-1])
67 |
68 | if unpadding > length {
69 | return nil, errors.New("unpad error. This could happen when incorrect encryption key is used")
70 | }
71 |
72 | return src[:(length - unpadding)], nil
73 | }
--------------------------------------------------------------------------------
/util/gofile/file.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package gofile
6 |
7 | import (
8 | "io/ioutil"
9 | "os"
10 | )
11 |
12 | func CreateFile(filePath string, data []byte, perm os.FileMode) error {
13 | return ioutil.WriteFile(filePath, data, perm)
14 | }
15 |
--------------------------------------------------------------------------------
/util/gois/is.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package gois
6 |
7 | import (
8 | "strings"
9 | "net"
10 | "regexp"
11 | )
12 |
13 | func IsInteger(val interface{}) bool {
14 | switch val.(type) {
15 | case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
16 | case string:
17 | str := val.(string)
18 | if str == "" {
19 | return false
20 | }
21 | str = strings.TrimSpace(str)
22 | if str[0] == '-' || str[0] == '+' {
23 | if len(str) == 1 {
24 | return false
25 | }
26 | str = str[1:]
27 | }
28 | for _, v := range str {
29 | if v < '0' || v > '9' {
30 | return false
31 | }
32 | }
33 | }
34 | return true
35 | }
36 |
37 | func IsIp(s string) bool {
38 | ip := net.ParseIP(s)
39 | if ip == nil {
40 | return false
41 | }
42 | return true
43 | }
44 |
45 | func IsEmail(s string) bool {
46 | pattern := `^[0-9A-Za-z][\.\-_0-9A-Za-z]*\@[0-9A-Za-z\-]+(\.[0-9A-Za-z]+)+$`
47 | reg := regexp.MustCompile(pattern)
48 | return reg.MatchString(s)
49 | }
50 |
--------------------------------------------------------------------------------
/util/golog/file.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package golog
6 |
7 | import (
8 | "os"
9 | "sync"
10 | )
11 |
12 | type FileHandler struct {
13 | filename string
14 | file *os.File
15 | mu sync.Mutex
16 | }
17 |
18 | func NewFileHandler(path string) *FileHandler {
19 | return &FileHandler{
20 | filename: path,
21 | }
22 | }
23 |
24 | func (f *FileHandler) Write(p []byte) (n int, err error) {
25 | f.mu.Lock()
26 | defer f.mu.Unlock()
27 |
28 | if err = f.openFileHandler(); err != nil {
29 | return 0, err
30 | }
31 |
32 | return f.file.Write(p)
33 | }
34 |
35 | func (f *FileHandler) Close() error {
36 | f.mu.Lock()
37 | defer f.mu.Unlock()
38 | return f.close()
39 | }
40 |
41 | func (f *FileHandler) close() error {
42 | if f.file == nil {
43 | return nil
44 | }
45 | err := f.file.Close()
46 | f.file = nil
47 | return err
48 | }
49 |
50 | func (f *FileHandler) openFileHandler() error {
51 | file, err := os.OpenFile(f.filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0755)
52 | if err != nil {
53 | return err
54 | }
55 | f.file = file
56 | return nil
57 | }
58 |
--------------------------------------------------------------------------------
/util/golog/log.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package golog
6 |
7 | import (
8 | "io"
9 | "os"
10 | "fmt"
11 | "time"
12 | "bytes"
13 | "strings"
14 | "sync"
15 | )
16 |
17 | const (
18 | LEVEL_DEBUG = iota
19 | LEVEL_INFO
20 | LEVEL_NOTICE
21 | LEVEL_WARNING
22 | LEVEL_ERROR
23 | LEVEL_PANIC
24 | )
25 |
26 | var levelMessage = map[int]string{
27 | LEVEL_DEBUG: "debug",
28 | LEVEL_INFO: "info",
29 | LEVEL_NOTICE: "notice",
30 | LEVEL_WARNING: "warning",
31 | LEVEL_ERROR: "error",
32 | LEVEL_PANIC: "panic",
33 | }
34 |
35 | type Logger struct {
36 | output io.Writer
37 | mu sync.Mutex
38 | }
39 |
40 | func New(out io.Writer) *Logger {
41 | logger := &Logger{
42 | output: out,
43 | }
44 | return logger
45 | }
46 |
47 | var stdLog = New(os.Stderr)
48 |
49 | func (l *Logger) Output(level int, s string) error {
50 | levelMsg, _ := levelMessage[level]
51 | var buf bytes.Buffer
52 | buf.WriteByte('[')
53 | buf.WriteString(time.Now().Format("2006-01-02 15:04:05"))
54 | buf.WriteString("] ")
55 | buf.WriteString(strings.ToUpper(levelMsg))
56 | buf.WriteString(": ")
57 | buf.WriteString(s)
58 | if len(s) == 0 || s[len(s)-1] != '\n' {
59 | buf.WriteByte('\n')
60 | }
61 | l.output.Write(buf.Bytes())
62 | return nil
63 | }
64 |
65 | func (l *Logger) SetOutput(w io.Writer) {
66 | l.mu.Lock()
67 | defer l.mu.Unlock()
68 | l.output = w
69 | }
70 |
71 | func (l *Logger) GetOutput() io.Writer {
72 | return l.output
73 | }
74 |
75 | func (l *Logger) Debug(s string, v ...interface{}) {
76 | l.Output(LEVEL_DEBUG, fmt.Sprintf(s, v...))
77 | }
78 |
79 | func (l *Logger) Info(s string, v ...interface{}) {
80 | l.Output(LEVEL_INFO, fmt.Sprintf(s, v...))
81 | }
82 |
83 | func (l *Logger) Notice(s string, v ...interface{}) {
84 | l.Output(LEVEL_NOTICE, fmt.Sprintf(s, v...))
85 | }
86 |
87 | func (l *Logger) Warning(s string, v ...interface{}) {
88 | l.Output(LEVEL_WARNING, fmt.Sprintf(s, v...))
89 | }
90 |
91 | func (l *Logger) Error(s string, v ...interface{}) {
92 | l.Output(LEVEL_ERROR, fmt.Sprintf(s, v...))
93 | }
94 |
95 | func (l *Logger) Panic(s string, v ...interface{}) {
96 | l.Output(LEVEL_PANIC, fmt.Sprintf(s, v...))
97 | panic(s)
98 | }
99 |
100 | func SetOutput(w io.Writer) {
101 | stdLog.SetOutput(w)
102 | }
103 |
104 | func Debug(s string, v ...interface{}) {
105 | stdLog.Debug(s, v...)
106 | }
107 |
108 | func Info(s string, v ...interface{}) {
109 | stdLog.Info(s, v...)
110 | }
111 |
112 | func Notice(s string, v ...interface{}) {
113 | stdLog.Notice(s, v...)
114 | }
115 |
116 | func Warning(s string, v ...interface{}) {
117 | stdLog.Warning(s, v...)
118 | }
119 |
120 | func Error(s string, v ...interface{}) {
121 | stdLog.Error(s, v...)
122 | }
123 |
124 | func Panic(s string, v ...interface{}) {
125 | stdLog.Panic(s, v...)
126 | }
--------------------------------------------------------------------------------
/util/gopath/path.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package gopath
6 |
7 | import (
8 | "os"
9 | "path/filepath"
10 | "strings"
11 | )
12 |
13 | func CurrentPath() (string, error) {
14 | dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
15 | if err != nil {
16 | return "", err
17 | }
18 | return dir, nil
19 | }
20 |
21 | func CurrentParentPath() (string, error) {
22 | path := strings.Join([]string{filepath.Dir(os.Args[0]), "/../"}, "")
23 | realPath, err := filepath.Abs(path)
24 | if err != nil {
25 | return "", err
26 | }
27 | return realPath, nil
28 | }
29 |
30 | func CreatePath(path string) error {
31 | exists := Exists(path)
32 | if !exists {
33 | if err := os.Mkdir(path, os.ModePerm); err != nil {
34 | return err
35 | }
36 | }
37 | return nil
38 | }
39 |
40 | func Exists(path string) bool {
41 | _, err := os.Stat(path)
42 | if err == nil {
43 | return true
44 | }
45 | if os.IsExist(err) {
46 | return true
47 | }
48 | return false
49 | }
50 |
51 | func IsDir(path string) bool {
52 | s, err := os.Stat(path)
53 | if err != nil {
54 | return false
55 | }
56 | return s.IsDir()
57 | }
58 |
59 | func IsFile(path string) bool {
60 | return !IsDir(path)
61 | }
62 |
63 | func RemovePath(path string) error {
64 | return os.RemoveAll(path)
65 | }
66 |
--------------------------------------------------------------------------------
/util/goslice/slice.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package goslice
6 |
7 | func FilterSliceInt(sl []int) []int {
8 | var newSlice []int
9 | for _, s := range sl {
10 | if s != 0 {
11 | newSlice = append(newSlice, s)
12 | }
13 | }
14 | return newSlice
15 | }
16 |
17 | func InSlice(v interface{}, sl []interface{}) bool {
18 | for _, vv := range sl {
19 | if vv == v {
20 | return true
21 | }
22 | }
23 | return false
24 | }
25 |
26 | func InSliceInt(v int, sl []int) bool {
27 | for _, vv := range sl {
28 | if vv == v {
29 | return true
30 | }
31 | }
32 | return false
33 | }
34 |
35 | func InSliceString(v string, sl []string) bool {
36 | for _, vv := range sl {
37 | if vv == v {
38 | return true
39 | }
40 | }
41 | return false
42 | }
43 |
44 | func SliceIntersect(slice1, slice2 []interface{}) (diffslice []interface{}) {
45 | for _, v := range slice1 {
46 | if InSlice(v, slice2) {
47 | diffslice = append(diffslice, v)
48 | }
49 | }
50 | return
51 | }
52 |
53 | func SliceIntersectInt(slice1, slice2 []int) (diffslice []int) {
54 | for _, v := range slice1 {
55 | if InSliceInt(v, slice2) {
56 | diffslice = append(diffslice, v)
57 | }
58 | }
59 | return
60 | }
61 |
62 | func SliceComplementaryInt(slice1, slice2 []int) (diffslice []int) {
63 | for _, v := range slice1 {
64 | if !InSliceInt(v, slice2) {
65 | diffslice = append(diffslice, v)
66 | }
67 | }
68 | return
69 | }
--------------------------------------------------------------------------------
/util/gostring/string.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 syncd Author. All Rights Reserved.
2 | // Use of this source code is governed by a MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package gostring
6 |
7 | import (
8 | "strings"
9 | "strconv"
10 | "math/rand"
11 | "time"
12 | "crypto/md5"
13 | "encoding/hex"
14 | "encoding/base64"
15 | "encoding/json"
16 | )
17 |
18 | func JoinStrings(multiString ...string) string {
19 | return strings.Join(multiString, "")
20 | }
21 |
22 | func JoinIntSlice2String(intSlice []int, sep string) string {
23 | return strings.Join(IntSlice2StrSlice(intSlice), sep)
24 | }
25 |
26 | func StrSplit2IntSlice(str, sep string) []int {
27 | return StrSlice2IntSlice(StrFilterSliceEmpty(strings.Split(str, sep)))
28 | }
29 |
30 | func Str2StrSlice(str, sep string) []string {
31 | return StrFilterSliceEmpty(strings.Split(str, sep))
32 | }
33 |
34 | func StrSlice2IntSlice(strSlice []string) []int {
35 | var intSlice []int
36 | for _, s := range strSlice {
37 | i, _ := strconv.Atoi(s)
38 | intSlice = append(intSlice, i)
39 | }
40 | return intSlice
41 | }
42 |
43 | func StrFilterSliceEmpty(strSlice []string) []string {
44 | var filterSlice []string
45 | for _, s := range strSlice {
46 | ss := strings.TrimSpace(s)
47 | if ss != "" {
48 | filterSlice = append(filterSlice, ss)
49 | }
50 | }
51 | return filterSlice
52 | }
53 |
54 | func IntSlice2StrSlice(intSlice []int) []string {
55 | var strSlice []string
56 | for _, i := range intSlice {
57 | s := strconv.Itoa(i)
58 | strSlice = append(strSlice, s)
59 | }
60 | return strSlice
61 | }
62 |
63 | func Str2Int(s string) int {
64 | i, _ := strconv.Atoi(s)
65 | return i
66 | }
67 |
68 | func Int2Str(i int) string {
69 | return strconv.Itoa(i)
70 | }
71 |
72 | func StrRandom(l int) string {
73 | str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
74 | bytes := []byte(str)
75 | result := []byte{}
76 | r := rand.New(rand.NewSource(time.Now().UnixNano()))
77 | for i := 0; i < l; i++ {
78 | result = append(result, bytes[r.Intn(len(bytes))])
79 | }
80 | return string(result)
81 | }
82 |
83 | func StrMd5(s string) string {
84 | md5Ctx := md5.New()
85 | md5Ctx.Write([]byte(s))
86 | return hex.EncodeToString(md5Ctx.Sum(nil))
87 | }
88 |
89 | func Base64Encode(b []byte) string {
90 | return base64.StdEncoding.EncodeToString(b)
91 | }
92 |
93 | func Base64Decode(s string) ([]byte, error) {
94 | ds, err := base64.StdEncoding.DecodeString(s)
95 | return ds, err
96 | }
97 |
98 | func Base64UrlEncode(b []byte) string {
99 | return base64.URLEncoding.EncodeToString(b)
100 | }
101 |
102 | func Base64UrlDecode(s string) ([]byte, error) {
103 | ds, err := base64.URLEncoding.DecodeString(s)
104 | return ds, err
105 | }
106 |
107 | func JsonEncode(obj interface{}) []byte {
108 | b, _ := json.Marshal(obj)
109 | return b
110 | }
111 |
112 | func JsonDecode(data []byte, obj interface{}) {
113 | json.Unmarshal(data, obj)
114 | }
115 |
--------------------------------------------------------------------------------
/web/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not ie <= 8
4 |
--------------------------------------------------------------------------------
/web/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
--------------------------------------------------------------------------------
/web/README.md:
--------------------------------------------------------------------------------
1 | # web
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn run build
16 | ```
17 |
18 | ### Run your tests
19 | ```
20 | yarn run test
21 | ```
22 |
23 | ### Lints and fixes files
24 | ```
25 | yarn run lint
26 | ```
27 |
28 | ### Customize configuration
29 | See [Configuration Reference](https://cli.vuejs.org/config/).
30 |
--------------------------------------------------------------------------------
/web/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build"
8 | },
9 | "dependencies": {
10 | "axios": "^0.19.1",
11 | "blueimp-md5": "^2.10.0",
12 | "codemirror": "^5.43.0",
13 | "element-ui": "^2.4.11",
14 | "js-cookie": "^2.2.0",
15 | "moment": "^2.24.0",
16 | "qs": "^6.6.0",
17 | "vue": "^2.5.21",
18 | "vue-i18n": "^8.7.0",
19 | "vue-router": "^3.0.1",
20 | "vuex": "^3.0.1"
21 | },
22 | "devDependencies": {
23 | "@fortawesome/fontawesome-free": "^5.6.3",
24 | "@vue/cli-plugin-babel": "^3.3.0",
25 | "@vue/cli-service": "^3.3.0",
26 | "node-sass": "^4.9.0",
27 | "sass-loader": "^7.0.1",
28 | "vue-template-compiler": "^2.5.21"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/web/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/web/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/web/public/favicon.ico
--------------------------------------------------------------------------------
/web/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Syncd - 自动化发布系统
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/web/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/web/src/api/deploy.js:
--------------------------------------------------------------------------------
1 | import {get, post} from '@/lib/fetch.js'
2 |
3 | export function applyProjectDetailApi(params) {
4 | return get('/deploy/apply/project/detail', params)
5 | }
6 |
7 | export function applySubmitApi(data) {
8 | return post('/deploy/apply/submit', data)
9 | }
10 |
11 | export function applyProjectAllApi() {
12 | return get('/deploy/apply/project/all')
13 | }
14 |
15 | export function applyListApi(params) {
16 | return get('/deploy/apply/list', params)
17 | }
18 |
19 | export function applyDetailApi(params) {
20 | return get('/deploy/apply/detail', params)
21 | }
22 |
23 | export function applyAuditApi(data) {
24 | return post('/deploy/apply/audit', data)
25 | }
26 |
27 | export function applyUpdateApi(data) {
28 | return post('/deploy/apply/update', data)
29 | }
30 |
31 | export function applyDropApi(data) {
32 | return post('/deploy/apply/drop', data)
33 | }
34 |
35 | export function applyRollbackListApi(params) {
36 | return get('/deploy/apply/rollbacklist', params)
37 | }
38 |
39 | export function buildStartApi(data) {
40 | return post('/deploy/build/start', data)
41 | }
42 |
43 | export function buildStatusApi(params) {
44 | return get('/deploy/build/status', params)
45 | }
46 |
47 | export function buildStopApi(data) {
48 | return post('/deploy/build/stop', data)
49 | }
50 |
51 | export function deployStart(data) {
52 | return post('/deploy/deploy/start', data)
53 | }
54 |
55 | export function deployStatusApi(params) {
56 | return get('/deploy/deploy/status', params)
57 | }
58 |
59 | export function deployStopApi(data) {
60 | return post('/deploy/deploy/stop', data)
61 | }
62 |
63 | export function deployRollbackApi(data) {
64 | return post('/deploy/deploy/rollback', data)
65 | }
--------------------------------------------------------------------------------
/web/src/api/login.js:
--------------------------------------------------------------------------------
1 | import {get, post} from '@/lib/fetch.js'
2 |
3 | export function loginApi(data) {
4 | return post('/login', data)
5 | }
6 |
7 | export function loginStatusApi() {
8 | return get("/login/status")
9 | }
10 |
11 | export function logoutApi() {
12 | return post('/logout')
13 | }
--------------------------------------------------------------------------------
/web/src/api/project.js:
--------------------------------------------------------------------------------
1 | import {get, post} from '@/lib/fetch.js'
2 |
3 | export function newSpaceApi(data) {
4 | return post('/project/space/add', data)
5 | }
6 |
7 | export function updateSpaceApi(data) {
8 | return post('/project/space/update', data)
9 | }
10 |
11 | export function listSpaceApi(params) {
12 | return get('/project/space/list', params)
13 | }
14 |
15 | export function detailSpaceApi(params) {
16 | return get('/project/space/detail', params)
17 | }
18 |
19 | export function deleteSpaceApi(data) {
20 | return post('/project/space/delete', data)
21 | }
22 |
23 | export function searchMemberApi(params) {
24 | return get('/project/member/search', params)
25 | }
26 |
27 | export function addMemberApi(data) {
28 | return post('/project/member/add', data)
29 | }
30 |
31 | export function listMemberApi(params) {
32 | return get('/project/member/list', params)
33 | }
34 |
35 | export function removeMemberApi(data) {
36 | return post('/project/member/remove', data)
37 | }
38 |
39 | export function newProjectApi(data) {
40 | return post('/project/add', data)
41 | }
42 |
43 | export function updateProjectApi(data) {
44 | return post('/project/update', data)
45 | }
46 |
47 | export function listProjectApi(params) {
48 | return get('/project/list', params)
49 | }
50 |
51 | export function switchStatusProjectApi(data) {
52 | return post('/project/switchstatus', data)
53 | }
54 |
55 | export function detailProjectApi(params) {
56 | return get('/project/detail', params)
57 | }
58 |
59 | export function deleteProjectApi(data) {
60 | return post('/project/delete', data)
61 | }
62 |
63 | export function updateBuildScriptApi(data) {
64 | return post('/project/buildscript', data)
65 | }
66 |
67 | export function updateHookScriptApi(data) {
68 | return post('/project/hookscript', data)
69 | }
70 |
--------------------------------------------------------------------------------
/web/src/api/server.js:
--------------------------------------------------------------------------------
1 | import {get, post} from '@/lib/fetch.js'
2 |
3 | export function newGroupApi(data) {
4 | return post('/server/group/add', data)
5 | }
6 |
7 | export function listGroupApi(params) {
8 | return get('/server/group/list', params)
9 | }
10 |
11 | export function deleteGroupApi(data) {
12 | return post('/server/group/delete', data)
13 | }
14 |
15 | export function detailGroupApi(params) {
16 | return get('/server/group/detail', params)
17 | }
18 |
19 | export function updateGroupApi(data) {
20 | return post('/server/group/update', data)
21 | }
22 |
23 | export function newServerApi(data) {
24 | return post('/server/add', data)
25 | }
26 |
27 | export function updateServerApi(data) {
28 | return post('/server/update', data)
29 | }
30 |
31 | export function listServerApi(params) {
32 | return get('/server/list', params)
33 | }
34 |
35 | export function deleteServerApi(data) {
36 | return post('/server/delete', data)
37 | }
38 |
39 | export function detailServerApi(params) {
40 | return get('/server/detail', params)
41 | }
--------------------------------------------------------------------------------
/web/src/api/user.js:
--------------------------------------------------------------------------------
1 | import {get, post} from '@/lib/fetch.js'
2 |
3 | export function privListApi() {
4 | return get('/user/role/privlist')
5 | }
6 |
7 | export function newRoleApi(data) {
8 | return post('/user/role/add', data)
9 | }
10 |
11 | export function listRoleApi(params) {
12 | return get('/user/role/list', params)
13 | }
14 |
15 | export function detailRoleApi(params) {
16 | return get('/user/role/detail', params)
17 | }
18 |
19 | export function updateRoleApi(data) {
20 | return post('/user/role/update', data)
21 | }
22 |
23 | export function deleteRoleApi(data) {
24 | return post('/user/role/delete', data)
25 | }
26 |
27 | export function newUserApi(data) {
28 | return post('/user/add', data)
29 | }
30 |
31 | export function updateUserApi(data) {
32 | return post('/user/update', data)
33 | }
34 |
35 | export function listUserApi(params) {
36 | return get('/user/list', params)
37 | }
38 |
39 | export function existsUserApi(params) {
40 | return get('/user/exists', params)
41 | }
42 |
43 | export function detailUserApi(params) {
44 | return get('/user/detail', params)
45 | }
46 |
47 | export function deleteUserApi(data) {
48 | return post('/user/delete', data)
49 | }
50 |
51 | export function userSettingApi(data) {
52 | return post('/user/my/setting', data)
53 | }
54 |
55 | export function userPasswordApi(data) {
56 | return post('/user/my/password', data)
57 | }
58 |
--------------------------------------------------------------------------------
/web/src/asset/login_bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/web/src/asset/login_bg.jpg
--------------------------------------------------------------------------------
/web/src/asset/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamans/syncd/71876dc6d3e7caa40e44a33ad47f8aa392881d40/web/src/asset/logo.png
--------------------------------------------------------------------------------
/web/src/component/ScrollBar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
42 |
43 |
55 |
--------------------------------------------------------------------------------
/web/src/lang/en.js:
--------------------------------------------------------------------------------
1 | export default {
2 |
3 | }
--------------------------------------------------------------------------------
/web/src/lang/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueI18n from 'vue-i18n'
3 |
4 | Vue.use(VueI18n)
5 |
6 | import zh from './zh_cn.js'
7 | import en from './en.js'
8 | const messages = {
9 | 'zh-cn': zh,
10 | 'en': en,
11 | }
12 |
13 | let localeLang
14 | if (global.navigator.language) {
15 | localeLang = global.navigator.language
16 | localeLang = localeLang.toLowerCase()
17 | }
18 | if (localeLang.indexOf('en') != 0) {
19 | localeLang = 'zh-cn'
20 | }
21 |
22 | const i18n = new VueI18n({
23 | locale: localeLang,
24 | messages,
25 | })
26 |
27 | export default i18n
--------------------------------------------------------------------------------
/web/src/lib/code.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'CODE_ERR_NETWORK': -1,
3 | 'CODE_OK': 0,
4 | 'CODE_ERR_SYSTEM': 1000,
5 | 'CODE_ERR_APP': 1001,
6 | 'CODE_ERR_PARAM': 1002,
7 | 'CODE_ERR_DATA_REPEAT': 1003,
8 | 'CODE_ERR_LOGIN_FAILED': 1004,
9 | 'CODE_ERR_NO_LOGIN': 1005,
10 | 'CODE_ERR_NO_PRIV': 1006,
11 | 'CODE_ERR_TASK_ERROR': 1007,
12 | 'CODE_ERR_USER_OR_PASS_WRONG': 1008,
13 | 'CODE_ERR_NO_DATA': 1009,
14 | }
--------------------------------------------------------------------------------
/web/src/lib/data.js:
--------------------------------------------------------------------------------
1 | import priv from '@/lib/priv'
2 |
3 | export default {
4 | PageSize: 0,
5 | Page: 0,
6 | Total: 0,
7 | DialogSmallWidth: '500px',
8 | DialogNormalWidth: '750px',
9 | DialogLargeWidth: '900px',
10 | DialogNormalTop: '5vh',
11 | Priv: priv,
12 |
13 | BuildStatusNone: 0,
14 | BuildStatusStart: 1,
15 | BuildStatusSuccess: 2,
16 | BuildStatusFailed: 3,
17 |
18 | ApplyStatusNone: 1,
19 | ApplyStatusIng: 2,
20 | ApplyStatusSuccess: 3,
21 | ApplyStatusFailed: 4,
22 | ApplyStatusDrop: 5,
23 | ApplyStatusRollback: 6,
24 |
25 | DeployModeBranch: 1,
26 | DeployModelTag: 2,
27 |
28 | BuildStatusNone: 0,
29 | BuildStatusStart: 1,
30 | BuildStatusSuccess: 2,
31 | BuildStatusFailed: 3,
32 |
33 | DeployGroupStatusNone: 0,
34 | DeployGroupStatusStart: 1,
35 | DeployGroupStatusSuccess: 2,
36 | DeployGroupStatusFailed: 3,
37 |
38 | AuditStatusPending: 1,
39 | AuditStatusOk: 2,
40 | AuditStatusRefuse: 3,
41 | }
--------------------------------------------------------------------------------
/web/src/lib/fetch.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import qs from 'qs'
3 | import Vue from 'vue'
4 | import i18n from '@/lang'
5 | import Code from './code.js'
6 | import Router from '@/router'
7 |
8 | let API_URL = '/api'
9 | let CancelToken = axios.CancelToken
10 |
11 | Vue.prototype.$CancelAjaxRequet = function() {}
12 | Vue.prototype.$IsCancel = axios.isCancel
13 |
14 | const service = axios.create({
15 | baseURL: API_URL + '/',
16 | timeout: 60000,
17 | withCredentials: true,
18 | })
19 |
20 | service.interceptors.request.use(config => {
21 | return config
22 | }, error => {
23 | Promise.reject(error)
24 | })
25 |
26 | service.interceptors.response.use(response => {
27 | let res = response.data
28 | if (!res) {
29 | res = {
30 | code: -1,
31 | message: i18n.t("network_error"),
32 | }
33 | }
34 | if (res.code != 0) {
35 | switch (res.code) {
36 | case Code.CODE_ERR_NETWORK:
37 | case Code.CODE_ERR_SYSTEM:
38 | case Code.CODE_ERR_APP:
39 | case Code.CODE_ERR_PARAM:
40 | Vue.prototype.$message.error(res.message)
41 | break
42 | case Code.CODE_ERR_NO_PRIV:
43 | Vue.prototype.$message.error('无操作权限')
44 | //Router.push({name: 'dashboard'})
45 | break
46 | case Code.CODE_ERR_NO_LOGIN:
47 | Vue.prototype.$message({
48 | message: '用户未登录',
49 | type: 'error',
50 | duration: 1000,
51 | onClose: () => {
52 | Router.push({name: 'login'})
53 | },
54 | })
55 | break
56 | }
57 | return Promise.reject(res)
58 | }
59 | return res.data
60 | }, error => {
61 | if (!axios.isCancel(error)) {
62 | let res = {
63 | code: -1,
64 | message: error.message ? error.message : i18n.t("unknown_error"),
65 | }
66 | Vue.prototype.$message.error(res.message)
67 | return Promise.reject(res)
68 | }
69 | return Promise.reject(error)
70 | })
71 |
72 | export function post(url, data, params, headers) {
73 | if (!params) {
74 | params = {}
75 | }
76 | params._t = new Date().getTime()
77 | let config = {
78 | method: 'post',
79 | url: url,
80 | params,
81 | }
82 | if (data) {
83 | if (headers && headers['Content-Type'] == 'multipart/form-data') {
84 | config.data = data
85 | } else {
86 | config.data = qs.stringify(data, { indices: false })
87 | }
88 | }
89 | if (headers) {
90 | config.headers = headers
91 | }
92 |
93 | config.cancelToken = new CancelToken(function(cancel) {
94 | Vue.prototype.$CancelAjaxRequet = function() {
95 | cancel()
96 | }
97 | })
98 |
99 | return service(config)
100 | }
101 |
102 | export function get(url, params, headers) {
103 | if (!params) {
104 | params = {}
105 | }
106 | params._t = new Date().getTime()
107 | let config = {
108 | method: 'get',
109 | url: url,
110 | params,
111 | }
112 | if (headers) {
113 | config.headers = headers
114 | }
115 |
116 | config.cancelToken = new CancelToken(function(cancel) {
117 | Vue.prototype.$CancelAjaxRequet = function() {
118 | cancel()
119 | }
120 | })
121 |
122 | return service(config)
123 | }
124 |
125 | export default service;
--------------------------------------------------------------------------------
/web/src/lib/priv.js:
--------------------------------------------------------------------------------
1 | export default {
2 | DEPLOY_APPLY: 1001,
3 | DEPLOY_VIEW: 1002,
4 | DEPLOY_AUDIT: 1003,
5 | DEPLOY_DEPLOY: 1004,
6 | DEPLOY_DROP: 1005,
7 | DEPLOY_EDIT: 1006,
8 |
9 | PROJECT_SPACE_VIEW: 2001,
10 | PROJECT_SPACE_NEW: 2002,
11 | PROJECT_SPACE_EDIT: 2003,
12 | PROJECT_SPACE_DEL: 2004,
13 | PROJECT_USER_VIEW: 2100,
14 | PROJECT_USER_NEW: 2101,
15 | PROJECT_USER_DEL: 2102,
16 | PROJECT_VIEW: 2201,
17 | PROJECT_NEW: 2202,
18 | PROJECT_EDIT: 2203,
19 | PROJECT_DEL: 2204,
20 | PROJECT_AUDIT: 2205,
21 | PROJECT_BUILD: 2206,
22 | PROJECT_HOOK: 2207,
23 |
24 | USER_ROLE_VIEW: 3001,
25 | USER_ROLE_NEW: 3002,
26 | USER_ROLE_EDIT: 3003,
27 | USER_ROLE_DEL: 3004,
28 | USER_VIEW: 3101,
29 | USER_NEW: 3102,
30 | USER_EDIT: 3103,
31 | USER_DEL: 3104,
32 |
33 | SERVER_GROUP_VIEW: 4001,
34 | SERVER_GROUP_NEW: 4002,
35 | SERVER_GROUP_EDIT: 4003,
36 | SERVER_GROUP_DEL: 4004,
37 | SERVER_VIEW: 4101,
38 | SERVER_NEW: 4102,
39 | SERVER_EDIT: 4103,
40 | SERVER_DEL: 4104,
41 | }
--------------------------------------------------------------------------------
/web/src/lib/util.js:
--------------------------------------------------------------------------------
1 | import md5 from 'blueimp-md5'
2 | import Cookies from 'js-cookie'
3 | import moment from 'moment'
4 |
5 | let loginTokenKey = '_syd_identity'
6 |
7 | export default {
8 |
9 | MessageSuccess(cb){
10 | this.$message({
11 | message: this.$t('operate_success'),
12 | type: 'success',
13 | duration: 1200,
14 | onClose: cb,
15 | });
16 | },
17 |
18 | PageInit() {
19 | this.$root.PageSize = 7
20 | this.$root.Page = 1
21 | this.$root.Total = 0
22 | },
23 |
24 | PageReset() {
25 | this.$root.Total--
26 | let maxPage = Math.ceil(this.$root.Total / this.$root.PageSize)
27 | if (this.$root.Page > maxPage) {
28 | this.$root.Page = maxPage
29 | }
30 | if (this.$root.Page < 1) {
31 | this.$root.Page = 1
32 | }
33 | },
34 |
35 | PageOffset() {
36 | return this.$root.PageSize * (this.$root.Page - 1)
37 | },
38 |
39 | ConfirmDelete(cb, title) {
40 | if (!title) {
41 | title = '此操作将永久删除该数据, 是否继续?'
42 | }
43 | this.$confirm(title, '提示', {
44 | confirmButtonText: '确定',
45 | cancelButtonText: '取消',
46 | type: 'warning'
47 | }).then(() => {
48 | cb()
49 | }).catch((err) => {
50 | console.log(err)
51 | })
52 | },
53 |
54 | Md5Sum(str) {
55 | return md5(str);
56 | },
57 |
58 | FormatDateTime(unixtime, format) {
59 | if (!unixtime) {
60 | return '--'
61 | }
62 | if (!format) {
63 | format = 'YYYY-MM-DD HH:mm:ss'
64 | }
65 | return moment.unix(unixtime).format(format)
66 | },
67 |
68 | FormatDateDuration(num) {
69 | return moment.duration(num).humanize(false)
70 | },
71 |
72 | FormatDateFromNow(unixtime) {
73 | if (!unixtime) {
74 | return '--'
75 | }
76 | return moment.unix(unixtime).fromNow()
77 | },
78 |
79 | Substr(str, len) {
80 | if (Object.prototype.toString.call(str) != '[object String]') {
81 | return ''
82 | }
83 | let postfix = ''
84 | if (str.length > len) {
85 | postfix = "..."
86 | }
87 | return str.substr(0, len) + postfix
88 | },
89 |
90 | SetLoginToken(token) {
91 | return Cookies.set(loginTokenKey, token)
92 | },
93 |
94 | GetLoginToken() {
95 | return Cookies.get(loginTokenKey)
96 | },
97 |
98 | DeleteLoginToken() {
99 | return Cookies.remove(loginTokenKey)
100 | },
101 |
102 | CheckPriv(code) {
103 | return this.$store.getters['account/getPriv'].indexOf(code) > -1
104 | },
105 |
106 | CheckPrivs(codeArr) {
107 | if (!codeArr || !codeArr.length) {
108 | return false
109 | }
110 | let checked = false
111 | codeArr.forEach(code => {
112 | if (this.CheckPriv(code)) {
113 | checked = true
114 | }
115 | })
116 | return checked
117 | },
118 | }
--------------------------------------------------------------------------------
/web/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import ElementUI from 'element-ui'
3 | import 'element-ui/lib/theme-chalk/index.css'
4 | import moment from 'moment'
5 | import App from './App.vue'
6 | import router from './router'
7 | import store from './store'
8 | import i18n from './lang'
9 | import util from './lib/util.js'
10 | import data from './lib/data.js'
11 | import './scss/app.scss'
12 |
13 |
14 | let localeLang
15 | if (global.navigator.language) {
16 | localeLang = global.navigator.language
17 | localeLang = localeLang.toLowerCase()
18 | }
19 | if (localeLang.indexOf('en') != 0) {
20 | localeLang = 'zh-cn'
21 | }
22 | moment.locale(localeLang)
23 |
24 | Vue.config.productionTip = false
25 | Vue.use(ElementUI);
26 |
27 | new Vue({
28 | i18n,
29 | router,
30 | store,
31 | methods: util,
32 | data: data,
33 | render: h => h(App)
34 | }).$mount('#app')
--------------------------------------------------------------------------------
/web/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | import account from './module/account.js'
5 |
6 | Vue.use(Vuex)
7 |
8 | export default new Vuex.Store({
9 | modules: {
10 | account,
11 | }
12 | })
--------------------------------------------------------------------------------
/web/src/store/module/account.js:
--------------------------------------------------------------------------------
1 | import md5 from 'blueimp-md5'
2 |
3 | const state = {
4 | user_id: 0,
5 | username: '',
6 | email: '',
7 | mobile: '',
8 | privilege: [],
9 | role_name: '',
10 | truename: '',
11 | }
12 |
13 | const getters = {
14 | isLogin(state) {
15 | return state.user_id > 0
16 | },
17 | getEmail(state) {
18 | return state.email
19 | },
20 | getUserName(state) {
21 | return state.username
22 | },
23 | getUserId(state) {
24 | return state.user_id
25 | },
26 | getAvatar(state) {
27 | return 'https://www.gravatar.com/avatar/' + md5(state.email.toLowerCase()) + '? s=512'
28 | },
29 | getPriv(state) {
30 | return state.privilege ? state.privilege : []
31 | },
32 | getRoleName(state) {
33 | return state.role_name
34 | },
35 | getMobile(state) {
36 | return state.mobile
37 | },
38 | getTrueName(state) {
39 | return state.truename
40 | },
41 | }
42 |
43 | const actions = {
44 | status({ commit }, userInfo) {
45 | commit('setUserInfo', {
46 | user_id: userInfo.user_id,
47 | username: userInfo.username,
48 | email: userInfo.email,
49 | mobile: userInfo.mobile,
50 | privilege: userInfo.privilege,
51 | role_name: userInfo.role_name,
52 | truename: userInfo.truename,
53 | })
54 | },
55 | userSetting({ commit }, userInfo) {
56 | commit('userSetting', {
57 | mobile: userInfo.mobile,
58 | truename: userInfo.truename,
59 | })
60 | },
61 | }
62 |
63 | const mutations = {
64 | setUserInfo(state, userInfo) {
65 | state.user_id = userInfo.user_id
66 | state.username = userInfo.username
67 | state.email = userInfo.email
68 | state.privilege = userInfo.privilege
69 | state.role_name = userInfo.role_name
70 | state.mobile = userInfo.mobile
71 | state.truename = userInfo.truename
72 | },
73 | userSetting(state, userInfo) {
74 | state.mobile = userInfo.mobile
75 | state.truename = userInfo.truename
76 | },
77 | }
78 |
79 | export default {
80 | namespaced: true,
81 | state,
82 | getters,
83 | actions,
84 | mutations
85 | }
86 |
--------------------------------------------------------------------------------
/web/src/view/Dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dashboard Coming Soon...
4 |
5 |
6 |
--------------------------------------------------------------------------------
/web/src/view/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ $t('welcome_to_login_syncd') }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{ $t('login') }}
16 |
17 |
18 |
19 |
20 |
21 | ©️ {{ new Date().getFullYear() }}
Syncd. All Rights Reserved. MIT License.
22 |
23 |
24 |
25 |
26 |
27 |
76 |
77 |
--------------------------------------------------------------------------------
/web/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | devServer: {
3 | proxy: {
4 | '/api': {
5 | target: 'http://localhost:8878/',
6 | changeOrigin: true,
7 | }
8 | }
9 | },
10 | publicPath: '/static/'
11 | }
12 |
--------------------------------------------------------------------------------