├── .gitignore
├── README.md
├── app
├── controllers
│ ├── agent.go
│ ├── base.go
│ ├── env.go
│ ├── mailtpl.go
│ ├── main.go
│ ├── project.go
│ ├── review.go
│ ├── role.go
│ ├── server.go
│ ├── task.go
│ ├── types.go
│ └── user.go
├── entity
│ ├── action.go
│ ├── env.go
│ ├── log.go
│ ├── mail_tpl.go
│ ├── perm.go
│ ├── project.go
│ ├── role.go
│ ├── server.go
│ ├── task.go
│ └── user.go
├── libs
│ ├── cmd.go
│ ├── functions.go
│ ├── pager.go
│ └── ssh.go
├── mail
│ └── mail.go
└── service
│ ├── action.go
│ ├── auth.go
│ ├── deploy.go
│ ├── deploy_job.go
│ ├── env.go
│ ├── init.go
│ ├── mail.go
│ ├── project.go
│ ├── repository.go
│ ├── role.go
│ ├── server.go
│ ├── system.go
│ ├── task.go
│ └── user.go
├── assets
├── avatars
│ ├── avatar.png
│ ├── avatar1.png
│ └── avatar2.png
├── css
│ ├── ace-ie.min.css
│ ├── ace-part2.min.css
│ ├── ace-rtl.min.css
│ ├── ace.min.css
│ ├── bootstrap-datetimepicker.min.css
│ ├── bootstrap-duallistbox.min.css
│ ├── bootstrap-editable.min.css
│ ├── bootstrap-multiselect.min.css
│ ├── bootstrap-timepicker.min.css
│ ├── bootstrap.min.css
│ ├── chosen-sprite.png
│ ├── chosen-sprite@2x.png
│ ├── chosen.min.css
│ ├── colorbox.min.css
│ ├── colorpicker.css
│ ├── colorpicker.min.css
│ ├── datepicker.min.css
│ ├── daterangepicker.min.css
│ ├── dropzone.min.css
│ ├── font-awesome.min.css
│ ├── fullcalendar.min.css
│ ├── images
│ │ ├── border.png
│ │ ├── controls.png
│ │ ├── loading.gif
│ │ ├── loading_background.png
│ │ ├── meteorshower2.jpg
│ │ ├── overlay.png
│ │ └── pattern.jpg
│ ├── img
│ │ ├── alpha.png
│ │ ├── hue.png
│ │ └── saturation.png
│ ├── jquery-ui.custom.min.css
│ ├── jquery-ui.min.css
│ ├── jquery.gritter.min.css
│ ├── prettify.min.css
│ ├── select2-spinner.gif
│ ├── select2.min.css
│ ├── select2.png
│ ├── select2x2.png
│ ├── ui.jqgrid.css
│ └── ui.jqgrid.min.css
├── font-awesome
│ ├── 4.1.0
│ │ ├── css
│ │ │ └── font-awesome.min.css
│ │ └── fonts
│ │ │ ├── fontawesome-webfont.eot
│ │ │ ├── fontawesome-webfont.svg
│ │ │ ├── fontawesome-webfont.ttf
│ │ │ └── fontawesome-webfont.woff
│ └── 4.2.0
│ │ ├── css
│ │ └── font-awesome.min.css
│ │ └── fonts
│ │ ├── fontawesome-webfont.eot
│ │ ├── fontawesome-webfont.svg
│ │ ├── fontawesome-webfont.ttf
│ │ └── fontawesome-webfont.woff
├── fonts
│ ├── DXI1ORHCpsQm3Vp6mXoaTXhCUOGz7vYGh680lGh-uXM.woff
│ ├── cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff
│ ├── fonts.googleapis.com.css
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ └── glyphicons-halflings-regular.woff
├── img
│ ├── clear.png
│ └── loading.gif
└── js
│ ├── ace-elements.min.js
│ ├── ace-extra.min.js
│ ├── ace.min.js
│ ├── additional-methods.min.js
│ ├── bootbox.min.js
│ ├── bootstrap-colorpicker.min.js
│ ├── bootstrap-datepicker.min.js
│ ├── bootstrap-datetimepicker.min.js
│ ├── bootstrap-editable.min.js
│ ├── bootstrap-markdown.min.js
│ ├── bootstrap-multiselect.min.js
│ ├── bootstrap-tag.min.js
│ ├── bootstrap-timepicker.min.js
│ ├── bootstrap-wysiwyg.min.js
│ ├── bootstrap.min.js
│ ├── chosen.jquery.min.js
│ ├── common.js
│ ├── daterangepicker.min.js
│ ├── dropzone.min.js
│ ├── excanvas.min.js
│ ├── fuelux.spinner.min.js
│ ├── fuelux.tree.min.js
│ ├── fuelux.wizard.min.js
│ ├── fullcalendar.min.js
│ ├── html5shiv.min.js
│ ├── jquery-ui.custom.min.js
│ ├── jquery-ui.min.js
│ ├── jquery.1.11.1.min.js
│ ├── jquery.2.1.1.min.js
│ ├── jquery.autosize.min.js
│ ├── jquery.bootstrap-duallistbox.min.js
│ ├── jquery.colorbox-min.js
│ ├── jquery.colorbox.min.js
│ ├── jquery.dataTables.bootstrap.js
│ ├── jquery.dataTables.bootstrap.min.js
│ ├── jquery.dataTables.min.js
│ ├── jquery.easypiechart.min.js
│ ├── jquery.flot.min.js
│ ├── jquery.flot.pie.min.js
│ ├── jquery.flot.resize.min.js
│ ├── jquery.gritter.min.js
│ ├── jquery.hotkeys.min.js
│ ├── jquery.inputlimiter.1.3.1.min.js
│ ├── jquery.maskedinput.min.js
│ ├── jquery.min.js
│ ├── jquery.mobile.custom.min.js
│ ├── jquery.nestable.min.js
│ ├── jquery.raty.min.js
│ ├── jquery.slimscroll.min.js
│ ├── jquery.sparkline.min.js
│ ├── jquery.ui.touch-punch.min.js
│ ├── jquery.validate.min.js
│ ├── markdown.min.js
│ ├── moment.min.js
│ ├── prettify.min.js
│ ├── respond.min.js
│ ├── select2.min.js
│ ├── spin.min.js
│ └── typeahead.jquery.min.js
├── conf
├── app.conf
├── locale_zh-CN.ini
└── menu.json
├── docker-compose.yaml
├── docker
├── Dockerfile
├── app.conf
├── my-custom.cnf
└── startup.sh
├── install.sql
├── logs
└── .gitkeep
├── main.go
├── pack.sh
├── screenshot.png
├── service.sh
└── views
├── agent
├── add.html
├── edit.html
├── list.html
└── projects.html
├── env
├── add.html
├── edit.html
└── list.html
├── error
└── message.html
├── layout
├── layout.html
└── sections
│ ├── footer.html
│ ├── header.html
│ ├── navbar.html
│ └── sidebar.html
├── log
└── list.html
├── mailtpl
├── add.html
├── edit.html
└── list.html
├── main
├── index.html
├── login.html
└── profile.html
├── project
├── add.html
├── edit.html
└── list.html
├── review
├── detail.html
├── list.html
└── review.html
├── role
├── add.html
├── edit.html
├── list.html
└── perm.html
├── server
├── add.html
├── edit.html
├── list.html
└── projects.html
├── task
├── create.html
├── create_step1.html
├── detail.html
├── list.html
├── publish-step1.html
├── publish-step2.html
└── publish-step3.html
└── user
├── add.html
├── edit.html
└── list.html
/.gitignore:
--------------------------------------------------------------------------------
1 | gopub*
2 | data/*
3 | logs/*.log
4 | *.tar.gz
5 | *.bak
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 版本发布系统
2 |
3 | 当前版本:v2.0.1
4 |
5 | 基于Git的代码发布系统,用于发布PHP等脚本语言开发的项目。使用Go语言和Beego框架开发。本人所在公司已使用了半年,累计超过五百次发版,到目前为止没出过什么问题,现在功能已经比较完善。
6 |
7 | ## 功能
8 |
9 | 1. 多帐号、多角色、权限管理
10 | 2. 发版邮件通知、邮件模板设置
11 | 3. 支持多个项目,每个项目可设置多个发布环境
12 | 4. 支持发版前、发版后执行指定shell脚本
13 | 5. 支持自动生成版本号文件
14 | 6. 支持发版审批,可针对不同项目选择开启
15 |
16 | ## 流程
17 |
18 | 整个发版流程如下:
19 |
20 | 1. 发布系统构建发布包
21 | 2. 将发布包发布到跳板机
22 | 3. 在跳板机进行解压,将代码同步到目标服务器。
23 |
24 | ## 下载地址
25 |
26 | - [https://github.com/lisijie/gopub/releases](https://github.com/lisijie/gopub/releases)
27 |
28 | ## 安装
29 |
30 | 仅支持linux/mac系统,并且要求安装了mysql和git。
31 |
32 | 安装步骤:
33 |
34 | 1. 创建数据库,将install.sql导入mysql。
35 | 2. 修改 conf/app.conf 中相关的配置。
36 | 3. 使用命令 `./service.sh start` 启动,如果无法启动,检查主程序 gopub 是否具有可执行权限,使用 `chmod +x ./gopub` 增加权限。
37 | 4. 使用 `http://localhost:8000` 访问。
38 | 5. 后台默认帐号为 `admin`,密码为 `admin888`。
39 |
40 | ## 使用docker运行
41 |
42 | 在源码目录使用docker-compose启动即可。
43 |
44 | $ docker-compose up
45 |
46 | ## 界面截图
47 |
48 | 
49 |
--------------------------------------------------------------------------------
/app/controllers/mailtpl.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/astaxie/beego"
5 | "github.com/lisijie/gopub/app/entity"
6 | "github.com/lisijie/gopub/app/service"
7 | )
8 |
9 | type MailTplController struct {
10 | BaseController
11 | }
12 |
13 | // 模板列表
14 | func (this *MailTplController) List() {
15 | list, _ := service.MailService.GetMailTplList()
16 | this.Data["pageTitle"] = "邮件模板"
17 | this.Data["list"] = list
18 | this.display()
19 | }
20 |
21 | // 添加模板
22 | func (this *MailTplController) Add() {
23 | if this.isPost() {
24 | name := this.GetString("name")
25 | subject := this.GetString("subject")
26 | content := this.GetString("content")
27 | mailTo := this.GetString("mail_to")
28 | mailCc := this.GetString("mail_cc")
29 |
30 | if name == "" || subject == "" || content == "" {
31 | this.showMsg("模板名称、邮件主题、邮件内容不能为空", MSG_ERR)
32 | }
33 |
34 | tpl := new(entity.MailTpl)
35 | tpl.UserId = this.auth.GetUserId()
36 | tpl.Name = name
37 | tpl.Subject = subject
38 | tpl.Content = content
39 | tpl.MailTo = mailTo
40 | tpl.MailCc = mailCc
41 | err := service.MailService.AddMailTpl(tpl)
42 | this.checkError(err)
43 |
44 | this.redirect(beego.URLFor("MailTplController.List"))
45 | }
46 |
47 | this.Data["pageTitle"] = "添加模板"
48 | this.display()
49 | }
50 |
51 | // 编辑模板
52 | func (this *MailTplController) Edit() {
53 | id, _ := this.GetInt("id")
54 | tpl, err := service.MailService.GetMailTpl(id)
55 | this.checkError(err)
56 |
57 | if this.isPost() {
58 | name := this.GetString("name")
59 | subject := this.GetString("subject")
60 | content := this.GetString("content")
61 | mailTo := this.GetString("mail_to")
62 | mailCc := this.GetString("mail_cc")
63 | if name == "" || subject == "" || content == "" {
64 | this.showMsg("模板名称、邮件主题、邮件内容不能为空", MSG_ERR)
65 | }
66 |
67 | tpl.Name = name
68 | tpl.Subject = subject
69 | tpl.Content = content
70 | tpl.MailTo = mailTo
71 | tpl.MailCc = mailCc
72 | err := service.MailService.SaveMailTpl(tpl)
73 | this.checkError(err)
74 |
75 | this.redirect(beego.URLFor("MailTplController.List"))
76 | }
77 |
78 | this.Data["pageTitle"] = "修改模板"
79 | this.Data["tpl"] = tpl
80 | this.display()
81 | }
82 |
83 | // 删除模板
84 | func (this *MailTplController) Del() {
85 | id, _ := this.GetInt("id")
86 |
87 | err := service.MailService.DelMailTpl(id)
88 | this.checkError(err)
89 |
90 | this.redirect(beego.URLFor("MailTplController.List"))
91 | }
92 |
--------------------------------------------------------------------------------
/app/controllers/review.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/astaxie/beego"
5 | "github.com/lisijie/gopub/app/entity"
6 | "github.com/lisijie/gopub/app/libs"
7 | "github.com/lisijie/gopub/app/service"
8 | )
9 |
10 | type ReviewController struct {
11 | BaseController
12 | }
13 |
14 | // 列表
15 | func (this *ReviewController) List() {
16 | status, _ := this.GetInt("status")
17 | page, _ := this.GetInt("page")
18 | startDate := this.GetString("start_date")
19 | endDate := this.GetString("end_date")
20 |
21 | if page < 1 {
22 | page = 1
23 | }
24 | filter := make([]interface{}, 0, 6)
25 | if status == 0 {
26 | filter = append(filter, "review_status", 0)
27 | } else {
28 | filter = append(filter, "review_status__in", []int{1, -1})
29 | }
30 | if startDate != "" {
31 | filter = append(filter, "start_date", startDate)
32 | }
33 | if endDate != "" {
34 | filter = append(filter, "end_date", endDate)
35 | }
36 |
37 | list, count := service.TaskService.GetList(page, this.pageSize, filter...)
38 | envList := make(map[int]*entity.Env)
39 | for k, v := range list {
40 | if _, ok := envList[v.PubEnvId]; !ok {
41 | envList[v.PubEnvId], _ = service.EnvService.GetEnv(v.PubEnvId)
42 | }
43 | list[k].EnvInfo = envList[v.PubEnvId]
44 | }
45 |
46 | this.Data["pageTitle"] = "审批列表"
47 | this.Data["status"] = status
48 | this.Data["count"] = count
49 | this.Data["list"] = list
50 | this.Data["pageBar"] = libs.NewPager(page, int(count), this.pageSize, beego.URLFor("ReviewController.List", "status", status, "start_date", startDate, "end_date", endDate), true).ToString()
51 | this.Data["startDate"] = startDate
52 | this.Data["endDate"] = endDate
53 | this.display()
54 | }
55 |
56 | // 审批
57 | func (this *ReviewController) Review() {
58 | id, _ := this.GetInt("id")
59 |
60 | if this.isPost() {
61 | status, _ := this.GetInt("status")
62 | message := this.GetString("message")
63 | err := service.TaskService.ReviewTask(id, this.userId, status, message)
64 | this.checkError(err)
65 | service.ActionService.Add("review_task", this.auth.GetUserName(), "task", id, this.GetString("status"))
66 | this.redirect(beego.URLFor("ReviewController.List"))
67 | }
68 |
69 | task, err := service.TaskService.GetTask(id)
70 | this.checkError(err)
71 | env, err := service.EnvService.GetEnv(task.PubEnvId)
72 | this.checkError(err)
73 |
74 | this.Data["pageTitle"] = "审批发布单"
75 | this.Data["env"] = env
76 | this.Data["task"] = task
77 | this.display()
78 | }
79 |
80 | // 详情
81 | func (this *ReviewController) Detail() {
82 | id, _ := this.GetInt("id")
83 |
84 | task, err := service.TaskService.GetTask(id)
85 | this.checkError(err)
86 | env, err := service.EnvService.GetEnv(task.PubEnvId)
87 | this.checkError(err)
88 | review, err := service.TaskService.GetReviewInfo(id)
89 | if err != nil {
90 | this.showMsg("审批记录不存在。", MSG_ERR)
91 | }
92 |
93 | this.Data["pageTitle"] = "浏览详情"
94 | this.Data["env"] = env
95 | this.Data["task"] = task
96 | this.Data["review"] = review
97 | this.Data["refer"] = this.Ctx.Request.Referer()
98 | this.display()
99 | }
100 |
--------------------------------------------------------------------------------
/app/controllers/role.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/astaxie/beego"
5 | "github.com/lisijie/gopub/app/entity"
6 | "github.com/lisijie/gopub/app/service"
7 | "strings"
8 | )
9 |
10 | type RoleController struct {
11 | BaseController
12 | }
13 |
14 | func (this *RoleController) List() {
15 | roleList, err := service.RoleService.GetAllRoles()
16 | this.checkError(err)
17 | for k, role := range roleList {
18 | roleList[k].UserList, _ = service.UserService.GetUserListByRoleId(role.Id)
19 | }
20 | this.Data["pageTitle"] = "角色管理"
21 | this.Data["list"] = roleList
22 | this.display()
23 | }
24 |
25 | func (this *RoleController) Add() {
26 | if this.isPost() {
27 | role := &entity.Role{}
28 | role.RoleName = this.GetString("role_name")
29 | role.Description = this.GetString("description")
30 | if role.RoleName == "" {
31 | this.showMsg("角色名不能为空", MSG_ERR)
32 | }
33 | err := service.RoleService.AddRole(role)
34 | this.checkError(err)
35 | this.redirect(beego.URLFor("RoleController.List"))
36 | }
37 | this.Data["pageTitle"] = "创建角色"
38 | this.display()
39 | }
40 |
41 | func (this *RoleController) Edit() {
42 | id, _ := this.GetInt("id")
43 | role, err := service.RoleService.GetRole(id)
44 | this.checkError(err)
45 |
46 | if this.isPost() {
47 | role.RoleName = this.GetString("role_name")
48 | role.Description = this.GetString("description")
49 | err := service.RoleService.UpdateRole(role, "RoleName", "Description")
50 | this.checkError(err)
51 | this.redirect(beego.URLFor("RoleController.List"))
52 | }
53 |
54 | this.Data["pageTitle"] = "编辑角色"
55 | this.Data["role"] = role
56 | this.display()
57 | }
58 |
59 | func (this *RoleController) Del() {
60 | id, _ := this.GetInt("id")
61 |
62 | err := service.RoleService.DeleteRole(id)
63 | this.checkError(err)
64 |
65 | this.redirect(beego.URLFor("RoleController.List"))
66 | }
67 |
68 | func (this *RoleController) Perm() {
69 | id, _ := this.GetInt("id")
70 | role, err := service.RoleService.GetRole(id)
71 | this.checkError(err)
72 |
73 | if this.isPost() {
74 | pids := this.GetStrings("pids")
75 | perms := this.GetStrings("perms")
76 | if len(pids) == 0 {
77 | role.ProjectIds = ""
78 | } else {
79 | role.ProjectIds = strings.Join(pids, ",")
80 | }
81 | err := service.RoleService.UpdateRole(role, "ProjectIds")
82 | this.checkError(err)
83 | err = service.RoleService.SetPerm(role.Id, perms)
84 | this.checkError(err)
85 | this.redirect(beego.URLFor("RoleController.List"))
86 | }
87 |
88 | projectList, _ := service.ProjectService.GetAllProject()
89 | permList := service.SystemService.GetPermList()
90 |
91 | chkmap := make(map[string]string)
92 | for _, v := range role.PermList {
93 | chkmap[v.Key] = "checked"
94 | }
95 | if role.ProjectIds != "" {
96 | pids := strings.Split(role.ProjectIds, ",")
97 | for _, pid := range pids {
98 | chkmap[pid] = "checked"
99 | }
100 | }
101 |
102 | this.Data["pageTitle"] = "编辑权限"
103 | this.Data["permList"] = permList
104 | this.Data["projectList"] = projectList
105 | this.Data["role"] = role
106 | this.Data["chkmap"] = chkmap
107 | this.display()
108 | }
109 |
--------------------------------------------------------------------------------
/app/controllers/server.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/astaxie/beego"
5 | "github.com/astaxie/beego/validation"
6 | "github.com/lisijie/gopub/app/entity"
7 | "github.com/lisijie/gopub/app/libs"
8 | "github.com/lisijie/gopub/app/service"
9 | "strconv"
10 | )
11 |
12 | type ServerController struct {
13 | BaseController
14 | }
15 |
16 | // 列表
17 | func (this *ServerController) List() {
18 | page, _ := strconv.Atoi(this.GetString("page"))
19 | if page < 1 {
20 | page = 1
21 | }
22 | count, err := service.ServerService.GetTotal(service.SERVER_TYPE_NORMAL)
23 | this.checkError(err)
24 | serverList, err := service.ServerService.GetServerList(page, this.pageSize)
25 | this.checkError(err)
26 |
27 | this.Data["count"] = count
28 | this.Data["list"] = serverList
29 | this.Data["pageBar"] = libs.NewPager(page, int(count), this.pageSize, beego.URLFor("ServerController.List"), true).ToString()
30 | this.Data["pageTitle"] = "服务器列表"
31 | this.display()
32 | }
33 |
34 | // 添加
35 | func (this *ServerController) Add() {
36 | if this.isPost() {
37 | valid := validation.Validation{}
38 | server := &entity.Server{}
39 | server.TypeId = service.SERVER_TYPE_NORMAL
40 | server.Ip = this.GetString("server_ip")
41 | server.Area = this.GetString("area")
42 | server.Description = this.GetString("description")
43 | valid.Required(server.Ip, "ip").Message("请输入服务器IP")
44 | valid.IP(server.Ip, "ip").Message("服务器IP无效")
45 | if valid.HasErrors() {
46 | for _, err := range valid.Errors {
47 | this.showMsg(err.Message, MSG_ERR)
48 | }
49 | }
50 |
51 | if err := service.ServerService.AddServer(server); err != nil {
52 | this.showMsg(err.Error(), MSG_ERR)
53 | }
54 |
55 | this.redirect(beego.URLFor("ServerController.List"))
56 | }
57 |
58 | this.Data["pageTitle"] = "添加服务器"
59 | this.display()
60 | }
61 |
62 | // 编辑
63 | func (this *ServerController) Edit() {
64 | id, _ := this.GetInt("id")
65 | server, err := service.ServerService.GetServer(id, service.SERVER_TYPE_NORMAL)
66 | this.checkError(err)
67 |
68 | if this.isPost() {
69 | valid := validation.Validation{}
70 | ip := this.GetString("server_ip")
71 | server.Area = this.GetString("area")
72 | server.Description = this.GetString("description")
73 | valid.Required(ip, "ip").Message("请输入服务器IP")
74 | valid.IP(ip, "ip").Message("服务器IP无效")
75 | if valid.HasErrors() {
76 | for _, err := range valid.Errors {
77 | this.showMsg(err.Message, MSG_ERR)
78 | }
79 | }
80 | server.Ip = ip
81 | err := service.ServerService.UpdateServer(server)
82 | this.checkError(err)
83 | this.redirect(beego.URLFor("ServerController.List"))
84 | }
85 |
86 | this.Data["pageTitle"] = "编辑服务器"
87 | this.Data["server"] = server
88 | this.display()
89 | }
90 |
91 | // 删除
92 | func (this *ServerController) Del() {
93 | id, _ := this.GetInt("id")
94 |
95 | _, err := service.ServerService.GetServer(id, service.SERVER_TYPE_NORMAL)
96 | this.checkError(err)
97 |
98 | err = service.ServerService.DeleteServer(id)
99 | this.checkError(err)
100 |
101 | this.redirect(beego.URLFor("ServerController.List"))
102 | }
103 |
104 | // 项目列表
105 | func (this *ServerController) Projects() {
106 | id, _ := this.GetInt("id")
107 | server, err := service.ServerService.GetServer(id, service.SERVER_TYPE_NORMAL)
108 | this.checkError(err)
109 | envList, err := service.EnvService.GetEnvListByServerId(id)
110 | this.checkError(err)
111 |
112 | result := make(map[int]map[string]interface{})
113 | for _, env := range envList {
114 | if _, ok := result[env.ProjectId]; !ok {
115 | project, err := service.ProjectService.GetProject(env.ProjectId)
116 | if err != nil {
117 | continue
118 | }
119 | row := make(map[string]interface{})
120 | row["projectId"] = project.Id
121 | row["projectName"] = project.Name
122 | row["envName"] = env.Name
123 | result[env.ProjectId] = row
124 | } else {
125 | result[env.ProjectId]["envName"] = result[env.ProjectId]["envName"].(string) + ", " + env.Name
126 | }
127 | }
128 |
129 | this.Data["list"] = result
130 | this.Data["server"] = server
131 | this.Data["pageTitle"] = server.Ip + " 下的项目列表"
132 | this.display()
133 | }
134 |
--------------------------------------------------------------------------------
/app/controllers/types.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | type Menu struct {
4 | Name string
5 | Route string
6 | Icon string
7 | Submenu []SubMenu
8 | }
9 |
10 | type SubMenu struct {
11 | Name string
12 | Route string
13 | Action string
14 | }
15 |
--------------------------------------------------------------------------------
/app/entity/action.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // 用户动作
8 | type Action struct {
9 | Id int
10 | Action string `orm:"size(20)"` // 动作类型
11 | Actor string `orm:"size(20)"` // 操作角色
12 | ObjectType string `orm:"size(20)"` // 操作对象类型
13 | ObjectId int `orm:"default(0)"` // 操作对象id
14 | Extra string `orm:"size(1000)"` // 额外信息
15 | CreateTime time.Time `orm:"auto_now;type(datetime)"` // 更新时间
16 | Message string `orm:"-"` // 格式化后的消息
17 | }
18 |
--------------------------------------------------------------------------------
/app/entity/env.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // 发布环境
8 | type Env struct {
9 | Id int
10 | ProjectId int `orm:"index"` // 项目id
11 | Name string `orm:"size(20)"` // 发布环境名称
12 | SshUser string `orm:"size(20)"` // 发布帐号
13 | SshPort string `orm:"size(10)"` // SSH端口
14 | SshKey string `orm:"size(100)"` // SSH KEY路径
15 | PubDir string `orm:"size(100)"` // 发布目录
16 | BeforeShell string `orm:"type(text)"` // 发布前执行的shell脚本
17 | AfterShell string `orm:"type(text)"` // 发布后执行的shell脚本
18 | ServerCount int `orm:"default(0)"` // 服务器数量
19 | SendMail int `orm:"default(0)"` // 是否发送发版邮件通知
20 | MailTplId int `orm:"default(0)"` // 邮件模板id
21 | MailTo string `orm:"size(1000)"` // 邮件收件人
22 | MailCc string `orm:"size(1000)"` // 邮件抄送人
23 | CreateTime time.Time `orm:"auto_now_add;type(datetime)"` // 创建时间
24 | UpdateTime time.Time `orm:"auto_now;type(datetime)"` // 更新时间
25 | ServerList []Server `orm:"-"` // 服务器列表
26 | }
27 |
28 | // 表结构
29 | type EnvServer struct {
30 | Id int
31 | ProjectId int `orm:"default(0)"` // 项目id
32 | EnvId int `orm:"default(0);index"` // 环境id
33 | ServerId int `orm:"default(0)"` // 服务器id
34 | }
35 |
--------------------------------------------------------------------------------
/app/entity/log.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // 表结构
8 | type Log struct {
9 | Id int
10 | UserId int
11 | Action string `orm:"size(100)"` // 操作
12 | Url string `orm:"size(200)"` // url
13 | PostData string `orm:"type(text)"` // 提交数据
14 | Message string `orm:"type(text)"` // 消息内容
15 | CreateTime time.Time `orm:"auto_now_add;type(datetime)"` // 创建时间
16 | }
17 |
--------------------------------------------------------------------------------
/app/entity/mail_tpl.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // 表结构
8 | type MailTpl struct {
9 | Id int
10 | UserId int
11 | Name string `orm:"size(100)"` // 模板名
12 | Subject string `orm:"size(200)"` // 邮件主题
13 | Content string `orm:"type(text)"` // 邮件内容
14 | MailTo string `orm:"size(1000)"` // 预设收件人
15 | MailCc string `orm:"size(1000)"` // 预设抄送人
16 | CreateTime time.Time `orm:"auto_now_add;type(datetime)"` // 创建时间
17 | UpdateTime time.Time `orm:"auto_now;type(datetime)"` // 更新时间
18 | }
19 |
--------------------------------------------------------------------------------
/app/entity/perm.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | type Perm struct {
4 | Module string `orm:"size(20)"`
5 | Action string `orm:"size(20)"`
6 | Key string `orm:"-"` // Module.Action
7 | }
8 |
9 | func (p *Perm) TableUnique() [][]string {
10 | return [][]string{
11 | []string{"Module", "Action"},
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/entity/project.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // 表结构
8 | type Project struct {
9 | Id int
10 | Name string `orm:"size(100)"` // 项目名称
11 | Domain string `orm:"size(100)"` // 项目标识
12 | Version string `orm:"size(20)"` // 最后发布版本
13 | VersionTime time.Time `orm:"type(datetime)"` // 最后发版时间
14 | RepoUrl string `orm:"size(100)"` // 仓库地址
15 | Status int `orm:"default(0)"` // 初始化状态
16 | ErrorMsg string `orm:"type(text)"` // 错误消息
17 | AgentId int `orm:"default(0)"` // 跳板机ID
18 | IgnoreList string `orm:"type(text)"` // 忽略文件列表
19 | BeforeShell string `orm:"type(text)"` // 发布前要执行的shell脚本
20 | AfterShell string `orm:"type(text)"` // 发布后要执行的shell脚本
21 | CreateVerfile int `orm:"default(0)"` // 是否生成版本号文件
22 | VerfilePath string `orm:"size(50)"` // 版本号文件目录
23 | TaskReview int `orm:"default(0)"` // 发布单是否需要经过审批
24 | CreateTime time.Time `orm:"auto_now_add;type(datetime)"` // 创建时间
25 | UpdateTime time.Time `orm:"auto_now;type(datetime)"` // 更新时间
26 | }
27 |
--------------------------------------------------------------------------------
/app/entity/role.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // 角色
8 | type Role struct {
9 | Id int
10 | RoleName string `orm:"size(20)"` // 角色名称
11 | ProjectIds string `orm:"size(1000)"` // 项目权限
12 | Description string `orm:"size(200)"` // 说明
13 | CreateTime time.Time `orm:"auto_now_add;type(datetime)"` // 创建时间
14 | UpdateTime time.Time `orm:"auto_now;type(datetime)"` // 更新时间
15 | PermList []Perm `orm:"-"` // 权限列表
16 | UserList []User `orm:"-"` // 用户列表
17 | }
18 |
19 | // 角色权限
20 | type RolePerm struct {
21 | RoleId int // 角色id
22 | Perm string `orm:"size(50)"` // 权限
23 | }
24 |
--------------------------------------------------------------------------------
/app/entity/server.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // 表结构
8 | type Server struct {
9 | Id int
10 | TypeId int // 0:普通服务器, 1:跳板机
11 | Ip string `orm:"size(20)"` // 服务器IP
12 | Area string `orm:"size(20)"` // 机房
13 | Description string `orm:"size(200)"` // 服务器说明
14 | SshPort int // ssh端口
15 | SshUser string `orm:"size(50)"` // ssh用户
16 | SshPwd string `orm:"size(100)"` // ssh密码
17 | SshKey string `orm:"size(100)"` // ssh key路径
18 | WorkDir string `orm:"size(100)"` // 工作目录
19 | CreateTime time.Time `orm:"auto_now_add;type(datetime)"` // 创建时间
20 | UpdateTime time.Time `orm:"auto_now;type(datetime)"` // 更新时间
21 | }
22 |
--------------------------------------------------------------------------------
/app/entity/task.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "strings"
7 | "time"
8 | )
9 |
10 | type Task struct {
11 | Id int
12 | ProjectId int `orm:"index"` // 项目id
13 | StartVer string `orm:"size(20)"` // 起始版本号
14 | EndVer string `orm:"size(20)"` // 结束版本号
15 | Message string `orm:"type(text)"` // 版本说明
16 | UserId int `orm:"index"` // 创建人ID
17 | UserName string `orm:"size(20)"` // 创建人名称
18 | BuildStatus int `orm:"default(0)"` // 构建状态
19 | ChangeLogs string `orm:"type(text)"` // 修改日志列表
20 | ChangeFiles string `orm:"type(text)"` // 修改文件列表
21 | Filepath string `orm:"size(200)"` // 更新包路径
22 | PubEnvId int `orm:"default(0)"` // 发布环境ID
23 | PubStatus int `orm:"default(0)"` // 发布状态:1 正在发布,2 发布到跳板机,3 发布到目标服务器,-2 发布到跳板机失败,-3 发布到目标服务器失败
24 | PubTime time.Time `orm:"null;type(datetime)"` // 发布时间
25 | ErrorMsg string `orm:"type(text)"` // 错误消息
26 | PubLog string `orm:"type(text)"` // 发布日志
27 | ReviewStatus int `orm:"default(0)"` // 审批状态
28 | CreateTime time.Time `orm:"auto_now_add;type(datetime)"` // 创建时间
29 | UpdateTime time.Time `orm:"auto_now;type(datetime)"` // 更新时间
30 | ProjectInfo *Project `orm:"-"` // 项目信息
31 | EnvInfo *Env `orm:"-"` // 发布环境
32 | }
33 |
34 | func (t *Task) GetChangeFileStat() string {
35 | var modifyNum, addNum, deleteNum int
36 | scaner := bufio.NewScanner(strings.NewReader(t.ChangeFiles))
37 | for scaner.Scan() {
38 | line := scaner.Bytes()
39 | switch line[0] {
40 | case 'M':
41 | modifyNum++
42 | case 'A':
43 | addNum++
44 | case 'D':
45 | deleteNum++
46 | }
47 | }
48 | return fmt.Sprintf("总数:%d,新增:%d,修改:%d,删除:%d", modifyNum+addNum+deleteNum, addNum, modifyNum, deleteNum)
49 | }
50 |
51 | type TaskReview struct {
52 | Id int
53 | TaskId int `orm:"default(0)"` // 任务id
54 | UserId int `orm:"default(0)"` // 审批人id
55 | UserName string `orm:"size(20)"` // 审批人
56 | Status int `orm:"default(0)"` // 审批结果(1:通过;0:不通过)
57 | Message string `orm:"type(text)"` // 审批说明
58 | CreateTime time.Time `orm:"auto_now_add;type(datetime)"` // 创建时间
59 | }
60 |
--------------------------------------------------------------------------------
/app/entity/user.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | type User struct {
8 | Id int
9 | UserName string `orm:"unique;size(20)"` // 用户名
10 | Password string `orm:"size(32)"` // 密码
11 | Salt string `orm:"size(10)"` // 密码盐
12 | Sex int `orm:"default(0)"` // 性别
13 | Email string `orm:"size(50)"` // 邮箱
14 | LastLogin time.Time `orm:"null;type(datetime)"` // 最后登录时间
15 | LastIp string `orm:"size(15)"` // 最后登录IP
16 | Status int `orm:"default(0)"` // 状态,0正常 -1禁用
17 | CreateTime time.Time `orm:"auto_now_add;type(datetime)"` // 创建时间
18 | UpdateTime time.Time `orm:"auto_now;type(datetime)"` // 更新时间
19 | RoleList []Role `orm:"-"` // 角色列表
20 | }
21 |
22 | type UserRole struct {
23 | UserId int // 用户id
24 | RoleId int // 角色id
25 | }
26 |
--------------------------------------------------------------------------------
/app/libs/functions.go:
--------------------------------------------------------------------------------
1 | package libs
2 |
3 | import (
4 | "crypto/md5"
5 | "fmt"
6 | "os"
7 | "path/filepath"
8 | "regexp"
9 | "strconv"
10 | "strings"
11 | )
12 |
13 | var emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?")
14 |
15 | const prettyLogFormat = `--pretty=format:%H`
16 |
17 | func RefEndName(refStr string) string {
18 | if strings.HasPrefix(refStr, "refs/heads/") {
19 | // trim the "refs/heads/"
20 | return refStr[len("refs/heads/"):]
21 | }
22 |
23 | index := strings.LastIndex(refStr, "/")
24 | if index != -1 {
25 | return refStr[index+1:]
26 | }
27 | return refStr
28 | }
29 |
30 | func filepathFromSHA1(rootdir, sha1 string) string {
31 | return filepath.Join(rootdir, "objects", sha1[:2], sha1[2:])
32 | }
33 |
34 | func IsDir(dir string) bool {
35 | f, e := os.Stat(dir)
36 | if e != nil {
37 | return false
38 | }
39 | return f.IsDir()
40 | }
41 |
42 | func IsFile(filePath string) bool {
43 | f, e := os.Stat(filePath)
44 | if e != nil {
45 | return false
46 | }
47 | return !f.IsDir()
48 | }
49 |
50 | func RealPath(filePath string) string {
51 | return os.ExpandEnv(filePath)
52 | }
53 |
54 | // 版本对比 v1比v2大返回1,小于返回-1,等于返回0
55 | func VerCompare(ver1, ver2 string) int {
56 | ver1 = strings.TrimLeft(ver1, "ver") // 清除v,e,r
57 | ver2 = strings.TrimLeft(ver2, "ver") // 清除v,e,r
58 | p1 := strings.Split(ver1, ".")
59 | p2 := strings.Split(ver2, ".")
60 |
61 | ver1 = ""
62 | for _, v := range p1 {
63 | iv, _ := strconv.Atoi(v)
64 | ver1 = fmt.Sprintf("%s%04d", ver1, iv)
65 | }
66 |
67 | ver2 = ""
68 | for _, v := range p2 {
69 | iv, _ := strconv.Atoi(v)
70 | ver2 = fmt.Sprintf("%s%04d", ver2, iv)
71 | }
72 |
73 | return strings.Compare(ver1, ver2)
74 | }
75 |
76 | // 生成md5
77 | func Md5(buf []byte) string {
78 | hash := md5.New()
79 | hash.Write(buf)
80 | return fmt.Sprintf("%x", hash.Sum(nil))
81 | }
82 |
83 | // 换行符换成
84 | func Nl2br(s string) string {
85 | s = strings.Replace(s, "\r\n", "\n", -1)
86 | s = strings.Replace(s, "\r", "\n", -1)
87 | s = strings.Replace(s, "\n", "
", -1)
88 | return s
89 | }
90 |
91 | func IsEmail(b []byte) bool {
92 | return emailPattern.Match(b)
93 | }
94 |
--------------------------------------------------------------------------------
/app/libs/pager.go:
--------------------------------------------------------------------------------
1 | package libs
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "math"
7 | "strings"
8 | )
9 |
10 | type Pager struct {
11 | Page int
12 | Totalnum int
13 | Pagesize int
14 | urlpath string
15 | urlquery string
16 | nopath bool
17 | }
18 |
19 | func NewPager(page, totalnum, pagesize int, url string, nopath ...bool) *Pager {
20 | p := new(Pager)
21 | p.Page = page
22 | p.Totalnum = totalnum
23 | p.Pagesize = pagesize
24 |
25 | arr := strings.Split(url, "?")
26 | p.urlpath = arr[0]
27 | if len(arr) > 1 {
28 | p.urlquery = "?" + arr[1]
29 | } else {
30 | p.urlquery = ""
31 | }
32 |
33 | if len(nopath) > 0 {
34 | p.nopath = nopath[0]
35 | } else {
36 | p.nopath = false
37 | }
38 |
39 | return p
40 | }
41 |
42 | func (this *Pager) url(page int) string {
43 | if this.nopath { //不使用目录形式
44 | if this.urlquery != "" {
45 | return fmt.Sprintf("%s%s&page=%d", this.urlpath, this.urlquery, page)
46 | } else {
47 | return fmt.Sprintf("%s?page=%d", this.urlpath, page)
48 | }
49 | } else {
50 | return fmt.Sprintf("%s/page/%d%s", this.urlpath, page, this.urlquery)
51 | }
52 | }
53 |
54 | func (this *Pager) ToString() string {
55 | if this.Totalnum <= this.Pagesize {
56 | return ""
57 | }
58 |
59 | var buf bytes.Buffer
60 | var from, to, linknum, offset, totalpage int
61 |
62 | offset = 5
63 | linknum = 10
64 |
65 | totalpage = int(math.Ceil(float64(this.Totalnum) / float64(this.Pagesize)))
66 |
67 | if totalpage < linknum {
68 | from = 1
69 | to = totalpage
70 | } else {
71 | from = this.Page - offset
72 | to = from + linknum
73 | if from < 1 {
74 | from = 1
75 | to = from + linknum - 1
76 | } else if to > totalpage {
77 | to = totalpage
78 | from = totalpage - linknum + 1
79 | }
80 | }
81 |
82 | buf.WriteString("
")
111 |
112 | return buf.String()
113 | }
114 |
--------------------------------------------------------------------------------
/app/libs/ssh.go:
--------------------------------------------------------------------------------
1 | package libs
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/pkg/sftp"
7 | "golang.org/x/crypto/ssh"
8 | "io"
9 | "io/ioutil"
10 | "os"
11 | "path"
12 | )
13 |
14 | type ServerConn struct {
15 | addr string // 192.168.1.1:22
16 | user string
17 | key string
18 | conn *ssh.Client
19 | sftpClient *sftp.Client
20 | }
21 |
22 | func NewServerConn(addr, user, key string) *ServerConn {
23 | key = RealPath(key)
24 | return &ServerConn{
25 | addr: addr,
26 | user: user,
27 | key: key,
28 | }
29 | }
30 |
31 | // 连接ssh服务器
32 | func (s *ServerConn) getSshConnect() (*ssh.Client, error) {
33 | if s.conn != nil {
34 | return s.conn, nil
35 | }
36 | config := ssh.ClientConfig{
37 | User: s.user,
38 | }
39 |
40 | keys := []ssh.Signer{}
41 | if pk, err := readPrivateKey(s.key); err == nil {
42 | keys = append(keys, pk)
43 | }
44 | config.Auth = append(config.Auth, ssh.PublicKeys(keys...))
45 |
46 | conn, err := ssh.Dial("tcp", s.addr, &config)
47 | if err != nil {
48 | return nil, fmt.Errorf("无法连接到服务器 [%s]: %v", s.addr, err)
49 | }
50 | s.conn = conn
51 | return s.conn, nil
52 | }
53 |
54 | // 返回sftp连接
55 | func (s *ServerConn) getSftpConnect() (*sftp.Client, error) {
56 | if s.sftpClient != nil {
57 | return s.sftpClient, nil
58 | }
59 |
60 | conn, err := s.getSshConnect()
61 | if err != nil {
62 | return nil, err
63 | }
64 |
65 | s.sftpClient, err = sftp.NewClient(conn, sftp.MaxPacket(1<<15))
66 | return s.sftpClient, err
67 | }
68 |
69 | // 关闭连接
70 | func (s *ServerConn) Close() {
71 | if s.conn != nil {
72 | s.conn.Close()
73 | s.conn = nil
74 | }
75 | if s.sftpClient != nil {
76 | s.sftpClient.Close()
77 | s.sftpClient = nil
78 | }
79 | }
80 |
81 | // 尝试连接服务器
82 | func (s *ServerConn) TryConnect() error {
83 | _, err := s.getSshConnect()
84 | if err != nil {
85 | return err
86 | }
87 | s.Close()
88 | return nil
89 | }
90 |
91 | // 在远程服务器执行命令
92 | func (s *ServerConn) RunCmd(cmd string) (string, error) {
93 |
94 | conn, err := s.getSshConnect()
95 | if err != nil {
96 | return "", err
97 | }
98 |
99 | session, err := conn.NewSession()
100 | if err != nil {
101 | return "", fmt.Errorf("创建会话失败: %v", err)
102 | }
103 | defer session.Close()
104 |
105 | var buf bytes.Buffer
106 |
107 | session.Stdout = &buf
108 | session.Stdin = &buf
109 |
110 | if err := session.Run(cmd); err != nil {
111 | return "", fmt.Errorf("执行命令失败: %v", err)
112 | }
113 |
114 | return buf.String(), nil
115 |
116 | }
117 |
118 | // 拷贝本机文件到远程服务器
119 | func (s *ServerConn) CopyFile(srcFile, dstFile string) error {
120 | client, err := s.getSftpConnect()
121 | if err != nil {
122 | return err
123 | }
124 |
125 | toPath := path.Dir(dstFile)
126 | if _, err := s.RunCmd("mkdir -p " + toPath); err != nil {
127 | return fmt.Errorf("创建目录失败:%v", err)
128 | }
129 |
130 | f, err := os.Open(srcFile)
131 | if err != nil {
132 | return fmt.Errorf("打开本地文件失败: %v", err)
133 | }
134 | defer f.Close()
135 |
136 | w, err := client.Create(dstFile)
137 | if err != nil {
138 | return fmt.Errorf("创建文件失败 [%s]: %v", dstFile, err)
139 | }
140 | defer w.Close()
141 |
142 | n, err := io.Copy(w, f)
143 | if err != nil {
144 | return fmt.Errorf("拷贝文件失败: %v", err)
145 | }
146 |
147 | fstat, _ := f.Stat()
148 | if fstat.Size() != n {
149 | return fmt.Errorf("写入文件大小错误,源文件大小:%d, 写入大小:%d", fstat.Size(), n)
150 | }
151 |
152 | return nil
153 | }
154 |
155 | func readPrivateKey(path string) (ssh.Signer, error) {
156 | f, err := os.Open(path)
157 | if err != nil {
158 | return nil, err
159 | }
160 | defer f.Close()
161 | b, err := ioutil.ReadAll(f)
162 | if err != nil {
163 | return nil, err
164 | }
165 | return ssh.ParsePrivateKey(b)
166 | }
167 |
--------------------------------------------------------------------------------
/app/mail/mail.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import (
4 | "github.com/astaxie/beego"
5 | "github.com/lisijie/gomail"
6 | "github.com/lisijie/gopub/app/libs"
7 | "strings"
8 | )
9 |
10 | var (
11 | host string
12 | port int
13 | username string
14 | password string
15 | from string
16 | )
17 |
18 | func init() {
19 | host = beego.AppConfig.String("mail.host")
20 | port, _ = beego.AppConfig.Int("mail.port")
21 | username = beego.AppConfig.String("mail.user")
22 | password = beego.AppConfig.String("mail.password")
23 | from = beego.AppConfig.String("mail.from")
24 | if port == 0 {
25 | port = 25
26 | }
27 | }
28 |
29 | func SendMail(subject, content string, to, cc []string) error {
30 | toList := make([]string, 0, len(to))
31 | ccList := make([]string, 0, len(cc))
32 |
33 | for _, v := range to {
34 | v = strings.TrimSpace(v)
35 | if libs.IsEmail([]byte(v)) {
36 | exists := false
37 | for _, vv := range toList {
38 | if v == vv {
39 | exists = true
40 | break
41 | }
42 | }
43 | if !exists {
44 | toList = append(toList, v)
45 | }
46 | }
47 | }
48 | for _, v := range cc {
49 | v = strings.TrimSpace(v)
50 | if libs.IsEmail([]byte(v)) {
51 | exists := false
52 | for _, vv := range ccList {
53 | if v == vv {
54 | exists = true
55 | break
56 | }
57 | }
58 | if !exists {
59 | ccList = append(ccList, v)
60 | }
61 | }
62 | }
63 |
64 | m := gomail.NewMessage()
65 | m.SetHeader("From", from)
66 | m.SetHeader("To", toList...)
67 | if len(ccList) > 0 {
68 | m.SetHeader("Cc", ccList...)
69 | }
70 | m.SetHeader("Subject", subject)
71 | m.SetBody("text/html", content)
72 |
73 | d := gomail.NewPlainDialer(host, port, username, password)
74 |
75 | return d.DialAndSend(m)
76 | }
77 |
--------------------------------------------------------------------------------
/app/service/action.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "fmt"
5 | "github.com/lisijie/gopub/app/entity"
6 | )
7 |
8 | // 系统动态
9 | type actionService struct{}
10 |
11 | func (this *actionService) table() string {
12 | return tableName("action")
13 | }
14 |
15 | // 添加记录
16 | func (this *actionService) Add(action, actor, objectType string, objectId int, extra string) bool {
17 | act := new(entity.Action)
18 | act.Action = action
19 | act.Actor = actor
20 | act.ObjectType = objectType
21 | act.ObjectId = objectId
22 | act.Extra = extra
23 | o.Insert(act)
24 | return true
25 | }
26 |
27 | // 登录动态
28 | func (this *actionService) Login(userName string, userId int, ip string) {
29 | this.Add("login", userName, "user", userId, ip)
30 | }
31 |
32 | // 退出登录
33 | func (this *actionService) Logout(userName string, userId int, ip string) {
34 | this.Add("logout", userName, "user", userId, ip)
35 | }
36 |
37 | // 更新个人信息
38 | func (this *actionService) UpdateProfile(userName string, userId int) {
39 | this.Add("update_profile", userName, "user", userId, "")
40 | }
41 |
42 | // 获取动态列表
43 | func (this *actionService) GetList(page, pageSize int) ([]entity.Action, error) {
44 | var list []entity.Action
45 | num, err := o.QueryTable(this.table()).OrderBy("-create_time").Offset((page - 1) * pageSize).Limit(pageSize).All(&list)
46 | if num > 0 && err == nil {
47 | for i := 0; i < int(num); i++ {
48 | this.format(&list[i])
49 | }
50 | }
51 | return list, err
52 | }
53 |
54 | // 格式化
55 | func (this *actionService) format(action *entity.Action) {
56 | switch action.Action {
57 | case "login":
58 | action.Message = fmt.Sprintf("%s 登录系统,IP为 %s。", action.Actor, action.Extra)
59 | case "logout":
60 | action.Message = fmt.Sprintf("%s 退出系统。", action.Actor)
61 | case "update_profile":
62 | action.Message = fmt.Sprintf("%s 更新了个人资料。", action.Actor)
63 | case "create_task":
64 | action.Message = fmt.Sprintf("%s 创建了编号为 %d 的发布单。", action.Actor, action.ObjectId)
65 | case "pub_task":
66 | task, err := TaskService.GetTask(action.ObjectId)
67 | if err != nil {
68 | action.Message = fmt.Sprintf("%s 发布了编号为 %d 版本。", action.Actor, action.ObjectId)
69 | } else {
70 | action.Message = fmt.Sprintf("%s 发布了 %s 的 %s 版本。", action.Actor, task.ProjectInfo.Name, task.EndVer)
71 | }
72 | case "del_task":
73 | action.Message = fmt.Sprintf("%s 删除了编号为 %d 的发布单。", action.Actor, action.ObjectId)
74 | case "review_task":
75 | task, err := TaskService.GetTask(action.ObjectId)
76 | if err != nil {
77 | if action.Extra == "1" {
78 | action.Message = fmt.Sprintf("%s 审批了编号为 %d 的发布单,结果为:通过", action.Actor, action.ObjectId)
79 | } else {
80 | action.Message = fmt.Sprintf("%s 审批了编号为 %d 的发布单,结果为:不通过", action.Actor, action.ObjectId)
81 | }
82 | } else {
83 | if action.Extra == "1" {
84 | action.Message = fmt.Sprintf("%s 审批了 %s 编号为%d的发布单,结果为:通过", action.Actor, task.ProjectInfo.Name, action.ObjectId)
85 | } else if action.Extra == "-1" {
86 | action.Message = fmt.Sprintf("%s 审批了 %s 编号为%d的发布单,结果为:不通过", action.Actor, task.ProjectInfo.Name, action.ObjectId)
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/service/auth.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/astaxie/beego"
7 | "github.com/astaxie/beego/orm"
8 | "github.com/lisijie/gopub/app/entity"
9 | "github.com/lisijie/gopub/app/libs"
10 | "strconv"
11 | "strings"
12 | "time"
13 | )
14 |
15 | // 登录验证服务
16 | type AuthService struct {
17 | loginUser *entity.User // 当前登录用户
18 | permMap map[string]bool // 当前用户权限表
19 | openPerm map[string]bool // 公开的权限
20 | }
21 |
22 | func NewAuth() *AuthService {
23 | return new(AuthService)
24 | }
25 |
26 | // 初始化开放权限
27 | func (this *AuthService) initOpenPerm() {
28 | this.openPerm = map[string]bool{
29 | "main.index": true,
30 | "main.profile": true,
31 | "main.login": true,
32 | "main.logout": true,
33 | "main.getpubstat": true,
34 | "project.clone": true,
35 | "project.getstatus": true,
36 | "task.gettags": true,
37 | "task.getstatus": true,
38 | "task.startpub": true,
39 | }
40 | }
41 |
42 | // 获取当前登录的用户对象
43 | func (this *AuthService) GetUser() *entity.User {
44 | return this.loginUser
45 | }
46 |
47 | // 获取当前登录的用户id
48 | func (this *AuthService) GetUserId() int {
49 | if this.IsLogined() {
50 | return this.loginUser.Id
51 | }
52 | return 0
53 | }
54 |
55 | // 获取当前登录的用户名
56 | func (this *AuthService) GetUserName() string {
57 | if this.IsLogined() {
58 | return this.loginUser.UserName
59 | }
60 | return ""
61 | }
62 |
63 | // 初始化
64 | func (this *AuthService) Init(token string) {
65 | this.initOpenPerm()
66 | arr := strings.Split(token, "|")
67 | beego.Trace("登录验证, token: ", token)
68 | if len(arr) == 2 {
69 | idstr, password := arr[0], arr[1]
70 | userId, _ := strconv.Atoi(idstr)
71 | if userId > 0 {
72 | user, err := UserService.GetUser(userId, true)
73 | if err == nil && password == libs.Md5([]byte(user.Password+user.Salt)) {
74 | this.loginUser = user
75 | this.initPermMap()
76 | beego.Trace("验证成功,用户信息: ", user)
77 | }
78 | }
79 | }
80 | }
81 |
82 | // 初始化权限表
83 | func (this *AuthService) initPermMap() {
84 | this.permMap = make(map[string]bool)
85 | for _, role := range this.loginUser.RoleList {
86 | for _, perm := range role.PermList {
87 | this.permMap[perm.Key] = true
88 | }
89 | }
90 | }
91 |
92 | // 检查是否有某个权限
93 | func (this *AuthService) HasAccessPerm(module, action string) bool {
94 | key := module + "." + action
95 | if !this.IsLogined() {
96 | return false
97 | }
98 | if this.loginUser.Id == 1 || this.isOpenPerm(key) {
99 | return true
100 | }
101 | if _, ok := this.permMap[key]; ok {
102 | return true
103 | }
104 | return false
105 | }
106 |
107 | // 检查是否登录
108 | func (this *AuthService) IsLogined() bool {
109 | return this.loginUser != nil && this.loginUser.Id > 0
110 | }
111 |
112 | // 是否公开访问的操作
113 | func (this *AuthService) isOpenPerm(key string) bool {
114 | if _, ok := this.openPerm[key]; ok {
115 | return true
116 | }
117 | return false
118 | }
119 |
120 | // 用户登录
121 | func (this *AuthService) Login(userName, password string) (string, error) {
122 | user, err := UserService.GetUserByName(userName)
123 | if err != nil {
124 | if err == orm.ErrNoRows {
125 | return "", errors.New("帐号或密码错误")
126 | } else {
127 | return "", errors.New("系统错误")
128 | }
129 | }
130 |
131 | if user.Password != libs.Md5([]byte(password+user.Salt)) {
132 | return "", errors.New("帐号或密码错误")
133 | }
134 | if user.Status == -1 {
135 | return "", errors.New("该帐号已禁用")
136 | }
137 |
138 | user.LastLogin = time.Now()
139 | UserService.UpdateUser(user, "LastLogin")
140 | this.loginUser = user
141 |
142 | token := fmt.Sprintf("%d|%s", user.Id, libs.Md5([]byte(user.Password+user.Salt)))
143 | return token, nil
144 | }
145 |
146 | // 退出登录
147 | func (this *AuthService) Logout() error {
148 | return nil
149 | }
150 |
--------------------------------------------------------------------------------
/app/service/env.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/astaxie/beego/orm"
5 | "github.com/lisijie/gopub/app/entity"
6 | )
7 |
8 | type envService struct{}
9 |
10 | func (this *envService) table() string {
11 | return tableName("env")
12 | }
13 | func (this *envService) serverTable() string {
14 | return tableName("env_server")
15 | }
16 |
17 | // 获取一个发布环境信息
18 | func (this *envService) GetEnv(id int) (*entity.Env, error) {
19 | env := &entity.Env{}
20 | env.Id = id
21 | err := o.Read(env)
22 | if err == nil {
23 | env.ServerList, _ = this.GetEnvServers(env.Id)
24 | }
25 | return env, err
26 | }
27 |
28 | // 获取某个项目的发布环境列表
29 | func (this *envService) GetEnvListByProjectId(projectId int) ([]entity.Env, error) {
30 | var list []entity.Env
31 | _, err := o.QueryTable(this.table()).Filter("project_id", projectId).All(&list)
32 | for _, env := range list {
33 | env.ServerList, _ = this.GetEnvServers(env.Id)
34 | }
35 | return list, err
36 | }
37 |
38 | // 根据服务器id发布环境列表
39 | func (this *envService) GetEnvListByServerId(serverId int) ([]entity.Env, error) {
40 | var (
41 | servList []entity.EnvServer
42 | envList []entity.Env
43 | )
44 | o.QueryTable(this.serverTable()).Filter("server_id", serverId).All(&servList)
45 | envIds := make([]int, 0, len(servList))
46 | for _, serv := range servList {
47 | envIds = append(envIds, serv.EnvId)
48 | }
49 | envList = make([]entity.Env, 0)
50 | if len(envIds) > 0 {
51 | if _, err := o.QueryTable(this.table()).Filter("id__in", envIds).All(&envList); err != nil {
52 | return envList, err
53 | }
54 | }
55 | return envList, nil
56 | }
57 |
58 | // 获取某个发布环境的服务器列表
59 | func (this *envService) GetEnvServers(envId int) ([]entity.Server, error) {
60 | var (
61 | list []entity.EnvServer
62 | )
63 | _, err := o.QueryTable(this.serverTable()).Filter("env_id", envId).All(&list)
64 | if err != nil {
65 | return nil, err
66 | }
67 | servIds := make([]int, 0, len(list))
68 | for _, v := range list {
69 | servIds = append(servIds, v.ServerId)
70 | }
71 |
72 | return ServerService.GetListByIds(servIds)
73 | }
74 |
75 | // 新增发布环境
76 | func (this *envService) AddEnv(env *entity.Env) error {
77 | env.ServerCount = len(env.ServerList)
78 | if _, err := o.Insert(env); err != nil {
79 | return err
80 | }
81 | for _, sv := range env.ServerList {
82 | es := new(entity.EnvServer)
83 | es.ProjectId = env.ProjectId
84 | es.EnvId = env.Id
85 | es.ServerId = sv.Id
86 | o.Insert(es)
87 | }
88 | return nil
89 | }
90 |
91 | // 保存环境配置
92 | func (this *envService) SaveEnv(env *entity.Env) error {
93 | env.ServerCount = len(env.ServerList)
94 | if _, err := o.Update(env); err != nil {
95 | return err
96 | }
97 | o.QueryTable(this.serverTable()).Filter("env_id", env.Id).Delete()
98 | for _, sv := range env.ServerList {
99 | es := new(entity.EnvServer)
100 | es.ProjectId = env.ProjectId
101 | es.EnvId = env.Id
102 | es.ServerId = sv.Id
103 | o.Insert(es)
104 | }
105 | return nil
106 | }
107 |
108 | // 删除发布环境
109 | func (this *envService) DeleteEnv(id int) error {
110 | o.QueryTable(this.table()).Filter("id", id).Delete()
111 | o.QueryTable(this.serverTable()).Filter("env_id", id).Delete()
112 | return nil
113 | }
114 |
115 | // 删除服务器
116 | func (this *envService) DeleteServer(serverId int) error {
117 | var envServers []entity.EnvServer
118 | o.QueryTable(this.serverTable()).Filter("server_id", serverId).All(&envServers)
119 | if len(envServers) < 1 {
120 | return nil
121 | }
122 | envIds := make([]int, 0, len(envServers))
123 | for _, v := range envServers {
124 | envIds = append(envIds, v.EnvId)
125 | }
126 | o.QueryTable(this.serverTable()).Filter("server_id", serverId).Delete()
127 | o.QueryTable(this.table()).Filter("id__in", envIds).Update(orm.Params{
128 | "server_count": orm.ColValue(orm.ColMinus, 1),
129 | })
130 | return nil
131 | }
132 |
--------------------------------------------------------------------------------
/app/service/init.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "fmt"
5 | "github.com/astaxie/beego"
6 | "github.com/astaxie/beego/orm"
7 | _ "github.com/go-sql-driver/mysql"
8 | "github.com/lisijie/gopub/app/entity"
9 | "net/url"
10 | "os"
11 | )
12 |
13 | var (
14 | o orm.Ormer
15 | tablePrefix string // 表前缀
16 | UserService *userService // 用户服务
17 | RoleService *roleService // 角色服务
18 | EnvService *envService // 发布环境服务
19 | ServerService *serverService // 服务器服务
20 | ProjectService *projectService // 项目服务
21 | MailService *mailService // 邮件服务
22 | TaskService *taskService // 任务服务
23 | DeployService *deployService // 部署服务
24 | RepositoryService *repositoryService // 仓库服务
25 | SystemService *systemService
26 | ActionService *actionService // 系统动态
27 | )
28 |
29 | func Init() {
30 | dbHost := beego.AppConfig.String("db.host")
31 | dbPort := beego.AppConfig.String("db.port")
32 | dbUser := beego.AppConfig.String("db.user")
33 | dbPassword := beego.AppConfig.String("db.password")
34 | dbName := beego.AppConfig.String("db.name")
35 | timezone := beego.AppConfig.String("db.timezone")
36 | tablePrefix = beego.AppConfig.String("db.prefix")
37 |
38 | if dbPort == "" {
39 | dbPort = "3306"
40 | }
41 | dsn := dbUser + ":" + dbPassword + "@tcp(" + dbHost + ":" + dbPort + ")/" + dbName + "?charset=utf8"
42 | if timezone != "" {
43 | dsn = dsn + "&loc=" + url.QueryEscape(timezone)
44 | }
45 | orm.RegisterDataBase("default", "mysql", dsn)
46 |
47 | orm.RegisterModelWithPrefix(tablePrefix,
48 | new(entity.Action),
49 | new(entity.Env),
50 | new(entity.EnvServer),
51 | new(entity.MailTpl),
52 | //new(entity.Perm),
53 | new(entity.Project),
54 | new(entity.Role),
55 | //new(entity.RolePerm),
56 | new(entity.Server),
57 | new(entity.Task),
58 | new(entity.TaskReview),
59 | new(entity.User),
60 | //new(entity.UserRole),
61 | )
62 |
63 | if beego.AppConfig.String("runmode") == "dev" {
64 | orm.Debug = true
65 | }
66 |
67 | o = orm.NewOrm()
68 | orm.RunCommand()
69 |
70 | // 创建代码
71 | os.MkdirAll(GetProjectsBasePath(), 0755)
72 | os.MkdirAll(GetTasksBasePath(), 0755)
73 |
74 | // 初始化服务对象
75 | initService()
76 | }
77 |
78 | func initService() {
79 | UserService = &userService{}
80 | RoleService = &roleService{}
81 | EnvService = &envService{}
82 | ServerService = &serverService{}
83 | ProjectService = &projectService{}
84 | MailService = &mailService{}
85 | TaskService = &taskService{}
86 | DeployService = &deployService{}
87 | RepositoryService = &repositoryService{}
88 | SystemService = &systemService{}
89 | ActionService = &actionService{}
90 | }
91 |
92 | // 返回真实表名
93 | func tableName(name string) string {
94 | return tablePrefix + name
95 | }
96 |
97 | func debug(v ...interface{}) {
98 | beego.Debug(v...)
99 | }
100 |
101 | // 任务单根目录
102 | func GetTasksBasePath() string {
103 | return fmt.Sprintf(beego.AppConfig.String("data_dir") + "/tasks")
104 | }
105 |
106 | // 所有项目根目录
107 | func GetProjectsBasePath() string {
108 | return fmt.Sprintf(beego.AppConfig.String("data_dir") + "/projects")
109 | }
110 |
111 | // 任务单目录
112 | func GetTaskPath(id int) string {
113 | return fmt.Sprintf(GetTasksBasePath()+"/task-%d", id)
114 | }
115 |
116 | // 某个项目的代码目录
117 | func GetProjectPath(name string) string {
118 | return GetProjectsBasePath() + "/" + name
119 | }
120 |
121 | func concatenateError(err error, stderr string) error {
122 | if len(stderr) == 0 {
123 | return err
124 | }
125 | return fmt.Errorf("%v: %s", err, stderr)
126 | }
127 |
128 | func DBVersion() string {
129 | var lists []orm.ParamsList
130 | o.Raw("SELECT VERSION()").ValuesList(&lists)
131 | return lists[0][0].(string)
132 | }
133 |
--------------------------------------------------------------------------------
/app/service/mail.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/lisijie/gopub/app/entity"
5 | )
6 |
7 | type mailService struct{}
8 |
9 | func (this *mailService) table() string {
10 | return tableName("mail_tpl")
11 | }
12 |
13 | func (this *mailService) AddMailTpl(tpl *entity.MailTpl) error {
14 | _, err := o.Insert(tpl)
15 | return err
16 | }
17 |
18 | func (this *mailService) DelMailTpl(id int) error {
19 | _, err := o.QueryTable(this.table()).Filter("id", id).Delete()
20 | return err
21 | }
22 |
23 | func (this *mailService) SaveMailTpl(tpl *entity.MailTpl) error {
24 | _, err := o.Update(tpl)
25 | return err
26 | }
27 |
28 | func (this *mailService) GetMailTpl(id int) (*entity.MailTpl, error) {
29 | tpl := &entity.MailTpl{}
30 | tpl.Id = id
31 | err := o.Read(tpl)
32 | return tpl, err
33 | }
34 |
35 | // 获取邮件模板列表
36 | func (this *mailService) GetMailTplList() ([]entity.MailTpl, error) {
37 | var list []entity.MailTpl
38 | _, err := o.QueryTable(this.table()).OrderBy("-id").All(&list)
39 | return list, err
40 | }
41 |
--------------------------------------------------------------------------------
/app/service/project.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/lisijie/gopub/app/entity"
5 | "os"
6 | )
7 |
8 | type projectService struct{}
9 |
10 | // 表名
11 | func (this *projectService) table() string {
12 | return tableName("project")
13 | }
14 |
15 | // 获取一个项目信息
16 | func (this *projectService) GetProject(id int) (*entity.Project, error) {
17 | project := &entity.Project{}
18 | project.Id = id
19 | if err := o.Read(project); err != nil {
20 | return nil, err
21 | }
22 | return project, nil
23 | }
24 |
25 | // 获取所有项目
26 | func (this *projectService) GetAllProject() ([]entity.Project, error) {
27 | return this.GetList(1, -1)
28 | }
29 |
30 | // 获取项目列表
31 | func (this *projectService) GetList(page, pageSize int) ([]entity.Project, error) {
32 | var list []entity.Project
33 | offset := 0
34 | if pageSize == -1 {
35 | pageSize = 100000
36 | } else {
37 | offset = (page - 1) * pageSize
38 | if offset < 0 {
39 | offset = 0
40 | }
41 | }
42 |
43 | _, err := o.QueryTable(this.table()).Offset(offset).Limit(pageSize).All(&list)
44 | return list, err
45 | }
46 |
47 | // 获取项目总数
48 | func (this *projectService) GetTotal() (int64, error) {
49 | return o.QueryTable(this.table()).Count()
50 | }
51 |
52 | // 添加项目
53 | func (this *projectService) AddProject(project *entity.Project) error {
54 | _, err := o.Insert(project)
55 | return err
56 | }
57 |
58 | // 更新项目信息
59 | func (this *projectService) UpdateProject(project *entity.Project, fields ...string) error {
60 | _, err := o.Update(project, fields...)
61 | return err
62 | }
63 |
64 | // 删除一个项目
65 | func (this *projectService) DeleteProject(projectId int) error {
66 | project, err := this.GetProject(projectId)
67 | if err != nil {
68 | return err
69 | }
70 | // 删除目录
71 | path := GetProjectPath(project.Domain)
72 | os.RemoveAll(path)
73 | // 环境配置
74 | if envList, err := EnvService.GetEnvListByProjectId(project.Id); err != nil {
75 | for _, env := range envList {
76 | EnvService.DeleteEnv(env.Id)
77 | }
78 | }
79 | // 删除任务
80 | TaskService.DeleteByProjectId(project.Id)
81 | // 删除项目
82 | o.Delete(project)
83 | return nil
84 | }
85 |
86 | // 克隆某个项目的仓库
87 | func (this *projectService) CloneRepo(projectId int) error {
88 | project, err := ProjectService.GetProject(projectId)
89 | if err != nil {
90 | return err
91 | }
92 |
93 | err = RepositoryService.CloneRepo(project.RepoUrl, GetProjectPath(project.Domain))
94 | if err != nil {
95 | project.Status = -1
96 | project.ErrorMsg = err.Error()
97 | } else {
98 | project.Status = 1
99 | }
100 | ProjectService.UpdateProject(project, "Status", "ErrorMsg")
101 |
102 | return err
103 | }
104 |
--------------------------------------------------------------------------------
/app/service/role.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "errors"
5 | "github.com/lisijie/gopub/app/entity"
6 | )
7 |
8 | type roleService struct{}
9 |
10 | func (this *roleService) table() string {
11 | return tableName("role")
12 | }
13 |
14 | // 根据id获取角色信息
15 | func (this *roleService) GetRole(id int) (*entity.Role, error) {
16 | role := &entity.Role{
17 | Id: id,
18 | }
19 | err := o.Read(role)
20 | if err != nil {
21 | return nil, err
22 | }
23 | this.loadRoleExtra(role)
24 | return role, err
25 | }
26 |
27 | // 根据名称获取角色
28 | func (this *roleService) GetRoleByName(roleName string) (*entity.Role, error) {
29 | role := &entity.Role{
30 | RoleName: roleName,
31 | }
32 | if err := o.Read(role, "RoleName"); err != nil {
33 | return nil, err
34 | }
35 | this.loadRoleExtra(role)
36 | return role, nil
37 | }
38 |
39 | func (this *roleService) loadRoleExtra(role *entity.Role) {
40 | o.Raw("SELECT SUBSTRING_INDEX(perm, '.', 1) as module,SUBSTRING_INDEX(perm, '.', -1) as `action`, perm AS `key` FROM "+tableName("role_perm")+" WHERE role_id = ?", role.Id).QueryRows(&role.PermList)
41 | }
42 |
43 | // 添加角色
44 | func (this *roleService) AddRole(role *entity.Role) error {
45 | if _, err := this.GetRoleByName(role.RoleName); err == nil {
46 | return errors.New("角色已存在")
47 | }
48 | _, err := o.Insert(role)
49 | return err
50 | }
51 |
52 | // 获取所有角色列表
53 | func (this *roleService) GetAllRoles() ([]entity.Role, error) {
54 | var (
55 | roles []entity.Role // 角色列表
56 | )
57 | if _, err := o.QueryTable(this.table()).All(&roles); err != nil {
58 | return nil, err
59 | }
60 | return roles, nil
61 | }
62 |
63 | // 更新角色信息
64 | func (this *roleService) UpdateRole(role *entity.Role, fields ...string) error {
65 | if v, err := this.GetRoleByName(role.RoleName); err == nil && v.Id != role.Id {
66 | return errors.New("角色名称已存在")
67 | }
68 | _, err := o.Update(role, fields...)
69 | return err
70 | }
71 |
72 | // 设置角色权限
73 | func (this *roleService) SetPerm(roleId int, perms []string) error {
74 | if _, err := this.GetRole(roleId); err != nil {
75 | return err
76 | }
77 | all := SystemService.GetPermList()
78 | pmmap := make(map[string]bool)
79 | for _, list := range all {
80 | for _, perm := range list {
81 | pmmap[perm.Key] = true
82 | }
83 | }
84 | for _, v := range perms {
85 | if _, ok := pmmap[v]; !ok {
86 | return errors.New("权限名称无效:" + v)
87 | }
88 | }
89 | o.Raw("DELETE FROM "+tableName("role_perm")+" WHERE role_id = ?", roleId).Exec()
90 | for _, v := range perms {
91 | o.Raw("REPLACE INTO "+tableName("role_perm")+" (role_id, perm) VALUES (?, ?)", roleId, v).Exec()
92 | }
93 | return nil
94 | }
95 |
96 | // 删除角色
97 | func (this *roleService) DeleteRole(id int) error {
98 | role, err := this.GetRole(id)
99 | if err != nil {
100 | return err
101 | }
102 | o.Delete(role)
103 | o.Raw("DELETE FROM "+tableName("role_user")+" WHERE role_id = ?", id).Exec()
104 | return nil
105 | }
106 |
--------------------------------------------------------------------------------
/app/service/server.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "errors"
5 | "github.com/lisijie/gopub/app/entity"
6 | )
7 |
8 | const (
9 | SERVER_TYPE_NORMAL = 0 // 普通web服务器
10 | SERVER_TYPE_AGENT = 1 // 跳板机
11 | )
12 |
13 | type serverService struct{}
14 |
15 | // 表名
16 | func (this *serverService) table() string {
17 | return tableName("server")
18 | }
19 |
20 | func (this *serverService) GetTotal(typeId int) (int64, error) {
21 | return o.QueryTable(this.table()).Filter("TypeId", typeId).Count()
22 | }
23 |
24 | // 获取一个服务器信息
25 | func (this *serverService) GetServer(id int, types ...int) (*entity.Server, error) {
26 | var err error
27 | server := &entity.Server{}
28 | server.Id = id
29 | if len(types) == 0 {
30 | err = o.Read(server)
31 | } else {
32 | err = o.QueryTable(this.table()).Filter("id", id).Filter("type_id", types[0]).One(server)
33 | }
34 | return server, err
35 | }
36 |
37 | // 根据id列表获取记录
38 | func (this *serverService) GetListByIds(ids []int) ([]entity.Server, error) {
39 | var list []entity.Server
40 | if len(ids) == 0 {
41 | return nil, errors.New("ids不能为空")
42 | }
43 | params := make([]interface{}, len(ids))
44 | for k, v := range ids {
45 | params[k] = v
46 | }
47 | _, err := o.QueryTable(this.table()).Filter("id__in", params...).All(&list)
48 | return list, err
49 | }
50 |
51 | // 获取普通服务器列表
52 | func (this *serverService) GetServerList(page, pageSize int) ([]entity.Server, error) {
53 | var list []entity.Server
54 | qs := o.QueryTable(this.table()).Filter("TypeId", SERVER_TYPE_NORMAL)
55 | if pageSize > 0 {
56 | qs = qs.Limit(pageSize, (page-1)*pageSize)
57 | }
58 | _, err := qs.All(&list)
59 | return list, err
60 | }
61 |
62 | // 获取跳板服务器列表
63 | func (this *serverService) GetAgentList(page, pageSize int) ([]entity.Server, error) {
64 | var list []entity.Server
65 | qs := o.QueryTable(this.table()).Filter("TypeId", SERVER_TYPE_AGENT)
66 | if pageSize > 0 {
67 | qs = qs.Limit(pageSize, (page-1)*pageSize)
68 | }
69 | _, err := qs.All(&list)
70 | return list, err
71 | }
72 |
73 | // 添加服务器
74 | func (this *serverService) AddServer(server *entity.Server) error {
75 | server.Id = 0
76 | if o.Read(server, "ip"); server.Id > 0 {
77 | return errors.New("服务器IP已存在:" + server.Ip)
78 | }
79 | _, err := o.Insert(server)
80 | return err
81 | }
82 |
83 | // 修改服务器信息
84 | func (this *serverService) UpdateServer(server *entity.Server, fields ...string) error {
85 | _, err := o.Update(server, fields...)
86 | return err
87 | }
88 |
89 | // 删除服务器
90 | func (this *serverService) DeleteServer(id int) error {
91 | _, err := o.QueryTable(this.table()).Filter("id", id).Delete()
92 | if err != nil {
93 | return err
94 | }
95 | return EnvService.DeleteServer(id)
96 | }
97 |
--------------------------------------------------------------------------------
/app/service/system.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/lisijie/gopub/app/entity"
5 | )
6 |
7 | type systemService struct{}
8 |
9 | func (this *systemService) GetPermList() map[string][]entity.Perm {
10 | var list []entity.Perm
11 | o.Raw("SELECT * FROM " + tableName("perm")).QueryRows(&list)
12 |
13 | result := make(map[string][]entity.Perm)
14 | for _, v := range list {
15 | v.Key = v.Module + "." + v.Action
16 | if _, ok := result[v.Module]; !ok {
17 | result[v.Module] = make([]entity.Perm, 0)
18 | }
19 | result[v.Module] = append(result[v.Module], v)
20 | }
21 | return result
22 | }
23 |
--------------------------------------------------------------------------------
/assets/avatars/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/avatars/avatar.png
--------------------------------------------------------------------------------
/assets/avatars/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/avatars/avatar1.png
--------------------------------------------------------------------------------
/assets/avatars/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/avatars/avatar2.png
--------------------------------------------------------------------------------
/assets/css/bootstrap-duallistbox.min.css:
--------------------------------------------------------------------------------
1 | .bootstrap-duallistbox-container .buttons{width:100%;margin-bottom:-1px}.bootstrap-duallistbox-container label{display:block}.bootstrap-duallistbox-container .info{display:inline-block;margin-bottom:5px;font-size:11px}.bootstrap-duallistbox-container .clear1,.bootstrap-duallistbox-container .clear2{display:none;font-size:10px}.bootstrap-duallistbox-container .box1.filtered .clear1,.bootstrap-duallistbox-container .box2.filtered .clear2{display:inline-block}.bootstrap-duallistbox-container .move,.bootstrap-duallistbox-container .remove{width:60%}.bootstrap-duallistbox-container .btn-group .btn{border-bottom-left-radius:0;border-bottom-right-radius:0}.bootstrap-duallistbox-container select{border-top-left-radius:0;border-top-right-radius:0}.bootstrap-duallistbox-container .moveall,.bootstrap-duallistbox-container .removeall{width:40%}.bootstrap-duallistbox-container.bs2compatible .btn-group>.btn+.btn{margin-left:0}.bootstrap-duallistbox-container select{width:100%;height:300px;padding:0}.bootstrap-duallistbox-container .filter{display:inline-block;width:100%;height:31px;margin:0 0 5px 0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-duallistbox-container .filter.placeholder{color:#aaa}.bootstrap-duallistbox-container.moveonselect .move,.bootstrap-duallistbox-container.moveonselect .remove{display:none}.bootstrap-duallistbox-container.moveonselect .moveall,.bootstrap-duallistbox-container.moveonselect .removeall{width:100%}
--------------------------------------------------------------------------------
/assets/css/bootstrap-multiselect.min.css:
--------------------------------------------------------------------------------
1 | .multiselect-container{position:absolute;list-style-type:none;margin:0;padding:0}.multiselect-container .input-group{margin:5px}.multiselect-container>li{padding:0}.multiselect-container>li>a.multiselect-all label{font-weight:700}.multiselect-container>li.multiselect-group label{margin:0;padding:3px 20px;height:100%;font-weight:700}.multiselect-container>li.multiselect-group-clickable label{cursor:pointer}.multiselect-container>li>a{padding:0}.multiselect-container>li>a>label{margin:0;height:100%;cursor:pointer;font-weight:400;padding:3px 20px 3px 40px}.multiselect-container>li>a>label.radio,.multiselect-container>li>a>label.checkbox{margin:0}.multiselect-container>li>a>label>input[type=checkbox]{margin-bottom:5px}.filter .btn{padding:6px 3px}.btn-group>.btn-group:nth-child(2)>.multiselect.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.form-inline .multiselect-container label.checkbox,.form-inline .multiselect-container label.radio{padding:3px 20px 3px 40px}.form-inline .multiselect-container li a label.checkbox input[type=checkbox],.form-inline .multiselect-container li a label.radio input[type=radio]{margin-left:-20px;margin-right:0}
--------------------------------------------------------------------------------
/assets/css/bootstrap-timepicker.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Timepicker Component for Twitter Bootstrap
3 | *
4 | * Copyright 2013 Joris de Wit
5 | *
6 | * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */.bootstrap-timepicker{position:relative}.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu{left:auto;right:0}.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:before{left:auto;right:12px}.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:after{left:auto;right:13px}.bootstrap-timepicker .add-on{cursor:pointer}.bootstrap-timepicker .add-on i{display:inline-block;width:16px;height:16px}.bootstrap-timepicker-widget.dropdown-menu{padding:4px}.bootstrap-timepicker-widget.dropdown-menu.open{display:inline-block}.bootstrap-timepicker-widget.dropdown-menu:before{border-bottom:7px solid rgba(0,0,0,.2);border-left:7px solid transparent;border-right:7px solid transparent;content:"";display:inline-block;position:absolute}.bootstrap-timepicker-widget.dropdown-menu:after{border-bottom:6px solid #FFF;border-left:6px solid transparent;border-right:6px solid transparent;content:"";display:inline-block;position:absolute}.bootstrap-timepicker-widget.timepicker-orient-left:before{left:6px}.bootstrap-timepicker-widget.timepicker-orient-left:after{left:7px}.bootstrap-timepicker-widget.timepicker-orient-right:before{right:6px}.bootstrap-timepicker-widget.timepicker-orient-right:after{right:7px}.bootstrap-timepicker-widget.timepicker-orient-top:before{top:-7px}.bootstrap-timepicker-widget.timepicker-orient-top:after{top:-6px}.bootstrap-timepicker-widget.timepicker-orient-bottom:before{bottom:-7px;border-bottom:0;border-top:7px solid #999}.bootstrap-timepicker-widget.timepicker-orient-bottom:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.bootstrap-timepicker-widget a.btn,.bootstrap-timepicker-widget input{border-radius:4px}.bootstrap-timepicker-widget table{width:100%;margin:0}.bootstrap-timepicker-widget table td{text-align:center;height:30px;margin:0;padding:2px}.bootstrap-timepicker-widget table td:not(.separator){min-width:30px}.bootstrap-timepicker-widget table td span{width:100%}.bootstrap-timepicker-widget table td a{border:1px transparent solid;width:100%;display:inline-block;margin:0;padding:8px 0;outline:0;color:#333}.bootstrap-timepicker-widget table td a:hover{text-decoration:none;background-color:#eee;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border-color:#ddd}.bootstrap-timepicker-widget table td a i{margin-top:2px;font-size:18px}.bootstrap-timepicker-widget table td input{width:25px;margin:0;text-align:center}.bootstrap-timepicker-widget .modal-content{padding:4px}@media (min-width:767px){.bootstrap-timepicker-widget.modal{width:200px;margin-left:-100px}}@media (max-width:767px){.bootstrap-timepicker{width:100%}.bootstrap-timepicker .dropdown-menu{width:100%}}
--------------------------------------------------------------------------------
/assets/css/chosen-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/chosen-sprite.png
--------------------------------------------------------------------------------
/assets/css/chosen-sprite@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/chosen-sprite@2x.png
--------------------------------------------------------------------------------
/assets/css/colorbox.min.css:
--------------------------------------------------------------------------------
1 | #colorbox,#cboxOverlay,#cboxWrapper{position:absolute;top:0;left:0;z-index:9999;overflow:hidden}#cboxWrapper{max-width:none}#cboxOverlay{position:fixed;width:100%;height:100%}#cboxMiddleLeft,#cboxBottomLeft{clear:left}#cboxContent{position:relative}#cboxLoadedContent{overflow:auto;-webkit-overflow-scrolling:touch}#cboxTitle{margin:0}#cboxLoadingOverlay,#cboxLoadingGraphic{position:absolute;top:0;left:0;width:100%;height:100%}#cboxPrevious,#cboxNext,#cboxClose,#cboxSlideshow{cursor:pointer}.cboxPhoto{float:left;margin:auto;border:0;display:block;max-width:none;-ms-interpolation-mode:bicubic}.cboxIframe{width:100%;height:100%;display:block;border:0;padding:0;margin:0}#colorbox,#cboxContent,#cboxLoadedContent{box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box}#cboxOverlay{background:url(images/overlay.png) repeat 0 0;opacity:.9;filter:alpha(opacity=90)}#colorbox{outline:0}#cboxTopLeft{width:21px;height:21px;background:url(images/controls.png) no-repeat -101px 0}#cboxTopRight{width:21px;height:21px;background:url(images/controls.png) no-repeat -130px 0}#cboxBottomLeft{width:21px;height:21px;background:url(images/controls.png) no-repeat -101px -29px}#cboxBottomRight{width:21px;height:21px;background:url(images/controls.png) no-repeat -130px -29px}#cboxMiddleLeft{width:21px;background:url(images/controls.png) left top repeat-y}#cboxMiddleRight{width:21px;background:url(images/controls.png) right top repeat-y}#cboxTopCenter{height:21px;background:url(images/border.png) 0 0 repeat-x}#cboxBottomCenter{height:21px;background:url(images/border.png) 0 -29px repeat-x}#cboxContent{background:#fff;overflow:hidden}.cboxIframe{background:#fff}#cboxError{padding:50px;border:1px solid #ccc}#cboxLoadedContent{margin-bottom:28px}#cboxTitle{position:absolute;bottom:4px;left:0;text-align:center;width:100%;color:#949494}#cboxCurrent{position:absolute;bottom:4px;left:58px;color:#949494}#cboxLoadingOverlay{background:url(images/loading_background.png) no-repeat center center}#cboxLoadingGraphic{background:url(images/loading.gif) no-repeat center center}#cboxPrevious,#cboxNext,#cboxSlideshow,#cboxClose{border:0;padding:0;margin:0;overflow:visible;width:auto;background:0 0}#cboxPrevious:active,#cboxNext:active,#cboxSlideshow:active,#cboxClose:active{outline:0}#cboxSlideshow{position:absolute;bottom:4px;right:30px;color:#0092ef}#cboxPrevious{position:absolute;bottom:0;left:0;background:url(images/controls.png) no-repeat -75px 0;width:25px;height:25px;text-indent:-9999px}#cboxPrevious:hover{background-position:-75px -25px}#cboxNext{position:absolute;bottom:0;left:27px;background:url(images/controls.png) no-repeat -50px 0;width:25px;height:25px;text-indent:-9999px}#cboxNext:hover{background-position:-50px -25px}#cboxClose{position:absolute;bottom:0;right:0;background:url(images/controls.png) no-repeat -25px 0;width:25px;height:25px;text-indent:-9999px}#cboxClose:hover{background-position:-25px -25px}.cboxIE #cboxTopLeft,.cboxIE #cboxTopCenter,.cboxIE #cboxTopRight,.cboxIE #cboxBottomLeft,.cboxIE #cboxBottomCenter,.cboxIE #cboxBottomRight,.cboxIE #cboxMiddleLeft,.cboxIE #cboxMiddleRight{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00FFFFFF, endColorstr=#00FFFFFF)}
--------------------------------------------------------------------------------
/assets/css/colorpicker.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Colorpicker for Bootstrap
3 | *
4 | * Copyright 2012 Stefan Petre
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | */
9 | .colorpicker-saturation {
10 | width: 100px;
11 | height: 100px;
12 | background-image: url(img/saturation.png);
13 | cursor: crosshair;
14 | float: left;
15 | }
16 | .colorpicker-saturation i {
17 | display: block;
18 | height: 5px;
19 | width: 5px;
20 | border: 1px solid #000;
21 | -webkit-border-radius: 5px;
22 | -moz-border-radius: 5px;
23 | border-radius: 5px;
24 | position: absolute;
25 | top: 0;
26 | left: 0;
27 | margin: -4px 0 0 -4px;
28 | }
29 | .colorpicker-saturation i b {
30 | display: block;
31 | height: 5px;
32 | width: 5px;
33 | border: 1px solid #fff;
34 | -webkit-border-radius: 5px;
35 | -moz-border-radius: 5px;
36 | border-radius: 5px;
37 | }
38 | .colorpicker-hue, .colorpicker-alpha {
39 | width: 15px;
40 | height: 100px;
41 | float: left;
42 | cursor: row-resize;
43 | margin-left: 4px;
44 | margin-bottom: 4px;
45 | }
46 | .colorpicker-hue i, .colorpicker-alpha i {
47 | display: block;
48 | height: 1px;
49 | background: #000;
50 | border-top: 1px solid #fff;
51 | position: absolute;
52 | top: 0;
53 | left: 0;
54 | width: 100%;
55 | margin-top: -1px;
56 | }
57 | .colorpicker-hue {
58 | background-image: url(img/hue.png);
59 | }
60 | .colorpicker-alpha {
61 | background-image: url(img/alpha.png);
62 | display: none;
63 | }
64 | .colorpicker {
65 | *zoom: 1;
66 | top: 0;
67 | left: 0;
68 | padding: 4px;
69 | min-width: 120px;
70 | margin-top: 1px;
71 | -webkit-border-radius: 4px;
72 | -moz-border-radius: 4px;
73 | border-radius: 4px;
74 | }
75 | .colorpicker:before, .colorpicker:after {
76 | display: table;
77 | content: "";
78 | }
79 | .colorpicker:after {
80 | clear: both;
81 | }
82 | .colorpicker:before {
83 | content: '';
84 | display: inline-block;
85 | border-left: 7px solid transparent;
86 | border-right: 7px solid transparent;
87 | border-bottom: 7px solid #ccc;
88 | border-bottom-color: rgba(0, 0, 0, 0.2);
89 | position: absolute;
90 | top: -7px;
91 | left: 6px;
92 | }
93 | .colorpicker:after {
94 | content: '';
95 | display: inline-block;
96 | border-left: 6px solid transparent;
97 | border-right: 6px solid transparent;
98 | border-bottom: 6px solid #ffffff;
99 | position: absolute;
100 | top: -6px;
101 | left: 7px;
102 | }
103 | .colorpicker div {
104 | position: relative;
105 | }
106 | .colorpicker.alpha {
107 | min-width: 140px;
108 | }
109 | .colorpicker.alpha .colorpicker-alpha {
110 | display: block;
111 | }
112 | .colorpicker-color {
113 | height: 10px;
114 | margin-top: 5px;
115 | clear: both;
116 | background-image: url(img/alpha.png);
117 | background-position: 0 100%;
118 | }
119 | .colorpicker-color div {
120 | height: 10px;
121 | }
122 | .input-append.color .add-on i, .input-prepend.color .add-on i {
123 | display: block;
124 | cursor: pointer;
125 | width: 16px;
126 | height: 16px;
127 | }
--------------------------------------------------------------------------------
/assets/css/colorpicker.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Colorpicker for Bootstrap
3 | *
4 | * Copyright 2012 Stefan Petre
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | */.colorpicker-saturation{width:100px;height:100px;background-image:url(img/saturation.png);cursor:crosshair;float:left}.colorpicker-saturation i{display:block;height:5px;width:5px;border:1px solid #000;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;position:absolute;top:0;left:0;margin:-4px 0 0 -4px}.colorpicker-saturation i b{display:block;height:5px;width:5px;border:1px solid #fff;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-hue,.colorpicker-alpha{width:15px;height:100px;float:left;cursor:row-resize;margin-left:4px;margin-bottom:4px}.colorpicker-hue i,.colorpicker-alpha i{display:block;height:1px;background:#000;border-top:1px solid #fff;position:absolute;top:0;left:0;width:100%;margin-top:-1px}.colorpicker-hue{background-image:url(img/hue.png)}.colorpicker-alpha{background-image:url(img/alpha.png);display:none}.colorpicker{*zoom:1;top:0;left:0;padding:4px;min-width:120px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.colorpicker:before,.colorpicker:after{display:table;content:""}.colorpicker:after{clear:both}.colorpicker:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,.2);position:absolute;top:-7px;left:6px}.colorpicker:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;top:-6px;left:7px}.colorpicker div{position:relative}.colorpicker.alpha{min-width:140px}.colorpicker.alpha .colorpicker-alpha{display:block}.colorpicker-color{height:10px;margin-top:5px;clear:both;background-image:url(img/alpha.png);background-position:0 100%}.colorpicker-color div{height:10px}.input-append.color .add-on i,.input-prepend.color .add-on i{display:block;cursor:pointer;width:16px;height:16px}
--------------------------------------------------------------------------------
/assets/css/images/border.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/images/border.png
--------------------------------------------------------------------------------
/assets/css/images/controls.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/images/controls.png
--------------------------------------------------------------------------------
/assets/css/images/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/images/loading.gif
--------------------------------------------------------------------------------
/assets/css/images/loading_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/images/loading_background.png
--------------------------------------------------------------------------------
/assets/css/images/meteorshower2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/images/meteorshower2.jpg
--------------------------------------------------------------------------------
/assets/css/images/overlay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/images/overlay.png
--------------------------------------------------------------------------------
/assets/css/images/pattern.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/images/pattern.jpg
--------------------------------------------------------------------------------
/assets/css/img/alpha.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/img/alpha.png
--------------------------------------------------------------------------------
/assets/css/img/hue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/img/hue.png
--------------------------------------------------------------------------------
/assets/css/img/saturation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/img/saturation.png
--------------------------------------------------------------------------------
/assets/css/jquery-ui.custom.min.css:
--------------------------------------------------------------------------------
1 | /*! jQuery UI - v1.11.2 - 2014-10-28
2 | * http://jqueryui.com
3 | * Includes: core.css, draggable.css, resizable.css, selectable.css, sortable.css, slider.css
4 | * Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted #000}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}
--------------------------------------------------------------------------------
/assets/css/jquery.gritter.min.css:
--------------------------------------------------------------------------------
1 | #gritter-notice-wrapper{position:fixed;top:20px;right:20px;width:301px;z-index:9999}#gritter-notice-wrapper.top-left{left:20px;right:auto}#gritter-notice-wrapper.bottom-right{top:auto;left:auto;bottom:20px;right:20px}#gritter-notice-wrapper.bottom-left{top:auto;right:auto;bottom:20px;left:20px}.gritter-item-wrapper{position:relative;margin:0 0 10px 0;background:url(../images/ie-spacer.gif)}.gritter-top{background:url(../images/gritter.png) no-repeat left -30px;height:10px}.hover .gritter-top{background-position:right -30px}.gritter-bottom{background:url(../images/gritter.png) no-repeat left bottom;height:8px;margin:0}.hover .gritter-bottom{background-position:bottom right}.gritter-item{display:block;background:url(../images/gritter.png) no-repeat left -40px;color:#eee;padding:2px 11px 8px 11px;font-size:11px;font-family:verdana}.hover .gritter-item{background-position:right -40px}.gritter-item p{padding:0;margin:0;word-wrap:break-word}.gritter-close{display:none;position:absolute;top:5px;left:3px;background:url(../images/gritter.png) no-repeat left top;cursor:pointer;width:30px;height:30px}.gritter-title{font-size:14px;font-weight:700;padding:0 0 7px 0;display:block;text-shadow:1px 1px 0 #000}.gritter-image{width:48px;height:48px;float:left}.gritter-with-image,.gritter-without-image{padding:0}.gritter-with-image{width:220px;float:right}.gritter-light .gritter-item,.gritter-light .gritter-bottom,.gritter-light .gritter-top,.gritter-light .gritter-close{background-image:url(../images/gritter-light.png);color:#222}.gritter-light .gritter-title{text-shadow:none}
--------------------------------------------------------------------------------
/assets/css/prettify.min.css:
--------------------------------------------------------------------------------
1 | .com{color:#93a1a1}.lit{color:#195f91}.pun,.opn,.clo{color:#93a1a1}.fun{color:#dc322f}.str,.atv{color:#D14}.kwd,.prettyprint .tag{color:#1e347b}.typ,.atn,.dec,.var{color:teal}.pln{color:#48484c}.prettyprint{padding:8px;background-color:#f7f7f9;border:1px solid #e1e1e8}.prettyprint.linenums{-webkit-box-shadow:inset 40px 0 0 #fbfbfc,inset 41px 0 0 #ececf0;box-shadow:inset 40px 0 0 #fbfbfc,inset 41px 0 0 #ececf0}ol.linenums{margin:0 0 0 33px}ol.linenums li{padding-left:12px;color:#bebec5;line-height:20px;text-shadow:0 1px 0 #fff}
--------------------------------------------------------------------------------
/assets/css/select2-spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/select2-spinner.gif
--------------------------------------------------------------------------------
/assets/css/select2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/select2.png
--------------------------------------------------------------------------------
/assets/css/select2x2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/css/select2x2.png
--------------------------------------------------------------------------------
/assets/font-awesome/4.1.0/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/font-awesome/4.1.0/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/assets/font-awesome/4.1.0/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/font-awesome/4.1.0/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/assets/font-awesome/4.1.0/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/font-awesome/4.1.0/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/assets/font-awesome/4.2.0/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/font-awesome/4.2.0/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/assets/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/assets/font-awesome/4.2.0/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/font-awesome/4.2.0/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/assets/fonts/DXI1ORHCpsQm3Vp6mXoaTXhCUOGz7vYGh680lGh-uXM.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/fonts/DXI1ORHCpsQm3Vp6mXoaTXhCUOGz7vYGh680lGh-uXM.woff
--------------------------------------------------------------------------------
/assets/fonts/cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/fonts/cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff
--------------------------------------------------------------------------------
/assets/fonts/fonts.googleapis.com.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Open Sans';
3 | font-style: normal;
4 | font-weight: 300;
5 | /*src: local('Open Sans Light'), local('OpenSans-Light'), url(/assets/fonts/DXI1ORHCpsQm3Vp6mXoaTXhCUOGz7vYGh680lGh-uXM.woff) format('woff');*/
6 | }
7 | @font-face {
8 | font-family: 'Open Sans';
9 | font-style: normal;
10 | font-weight: 400;
11 | /*src: local('Open Sans'), local('OpenSans'), url(/assets/fonts/cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff) format('woff');*/
12 | }
13 |
--------------------------------------------------------------------------------
/assets/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/assets/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/assets/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/assets/img/clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/img/clear.png
--------------------------------------------------------------------------------
/assets/img/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/assets/img/loading.gif
--------------------------------------------------------------------------------
/assets/js/bootstrap-tag.min.js:
--------------------------------------------------------------------------------
1 | !function(a){"use strict";var b=function(b,c){this.element=a(b),this.options=a.extend(!0,{},a.fn.tag.defaults,c),this.values=a.grep(a.map(this.element.val().split(","),a.trim),function(a){return a.length>0}),this.show()};b.prototype={constructor:b,show:function(){var b=this;b.element.parent().prepend(b.element.detach().hide()),b.element.wrap(a('')).parent().on("click",function(){b.input.focus()}),b.values.length&&a.each(b.values,function(){b.createBadge(this)}),b.input=a('
').attr("placeholder",b.options.placeholder).insertAfter(b.element).on("focus",function(){b.element.parent().addClass("tags-hover")}).on("blur",function(){b.skip||(b.process(),b.element.parent().removeClass("tags-hover"),b.element.siblings(".tag").removeClass("tag-important")),b.skip=!1}).on("keydown",function(c){if(188==c.keyCode||13==c.keyCode||9==c.keyCode)!a.trim(a(this).val())||b.element.siblings(".typeahead").length&&!b.element.siblings(".typeahead").is(":hidden")?188==c.keyCode&&(!b.element.siblings(".typeahead").length||b.element.siblings(".typeahead").is(":hidden")?c.preventDefault():(b.input.data("typeahead").select(),c.stopPropagation(),c.preventDefault())):(9!=c.keyCode&&c.preventDefault(),b.process());else if(a.trim(a(this).val())||8!=c.keyCode)b.element.siblings(".tag").removeClass("tag-important");else{var d=b.element.siblings(".tag").length;if(d){var e=b.element.siblings(".tag:eq("+(d-1)+")");e.hasClass("tag-important")?b.remove(d-1):e.addClass("tag-important")}}}).bs_typeahead({source:b.options.source,matcher:function(a){return~a.toLowerCase().indexOf(this.query.toLowerCase())&&(-1==b.inValues(a)||b.options.allowDuplicates)},updater:a.proxy(b.add,b)}),a(b.input.data("bs_typeahead").$menu).on("mousedown",function(){b.skip=!0}),this.element.trigger("shown")},inValues:function(b){if(this.options.caseInsensitive){var c=-1;return a.each(this.values,function(a,d){return d.toLowerCase()==b.toLowerCase()?(c=a,!1):void 0}),c}return a.inArray(b,this.values)},createBadge:function(b){var c=this;a("
",{"class":"tag"}).text(b).append(a('
').on("click",function(){c.remove(c.element.siblings(".tag").index(a(this).closest(".tag")))})).insertBefore(c.element)},add:function(b){var c=this;if(!c.options.allowDuplicates){var d=c.inValues(b);if(-1!=d){var e=c.element.siblings(".tag:eq("+d+")");return e.addClass("tag-warning"),void setTimeout(function(){a(e).removeClass("tag-warning")},500)}}this.values.push(b),this.createBadge(b),this.element.val(this.values.join(", ")),this.element.trigger("added",[b])},remove:function(a){if(a>=0){var b=this.values.splice(a,1);this.element.siblings(".tag:eq("+a+")").remove(),this.element.val(this.values.join(", ")),this.element.trigger("removed",[b])}},process:function(){var b=a.grep(a.map(this.input.val().split(","),a.trim),function(a){return a.length>0}),c=this;a.each(b,function(){c.add(this)}),this.input.val("")},skip:!1};var c=a.fn.tag;a.fn.tag=function(c){return this.each(function(){var d=a(this),e=d.data("tag"),f="object"==typeof c&&c;e||d.data("tag",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.tag.defaults={allowDuplicates:!1,caseInsensitive:!0,placeholder:"",source:[]},a.fn.tag.Constructor=b,a.fn.tag.noConflict=function(){return a.fn.tag=c,this},a(window).on("load",function(){a('[data-provide="tag"]').each(function(){var b=a(this);b.data("tag")||b.tag(b.data())})})}(window.jQuery);
--------------------------------------------------------------------------------
/assets/js/bootstrap-wysiwyg.min.js:
--------------------------------------------------------------------------------
1 | !function(a){"use strict";var b=function(b){var c=a.Deferred(),d=new FileReader;return d.onload=function(a){c.resolve(a.target.result)},d.onerror=c.reject,d.onprogress=c.notify,d.readAsDataURL(b),c.promise()};a.fn.cleanHtml=function(){var b=a(this).html();return b&&b.replace(/(
|\s|
<\/div>| )*$/,"")},a.fn.wysiwyg=function(c){var d,e,f,g=this,h=function(){e.activeToolbarClass&&a(e.toolbarSelector).find(f).each(function(){try{var b=a(this).data(e.commandRole);document.queryCommandState(b)?a(this).addClass(e.activeToolbarClass):a(this).removeClass(e.activeToolbarClass)}catch(c){}})},i=function(a,b){var c=a.split(" "),d=c.shift(),e=c.join(" ")+(b||"");document.execCommand(d,0,e),h()},j=function(b){a.each(b,function(a,b){g.keydown(a,function(a){g.attr("contenteditable")&&g.is(":visible")&&(a.preventDefault(),a.stopPropagation(),i(b))}).keyup(a,function(a){g.attr("contenteditable")&&g.is(":visible")&&(a.preventDefault(),a.stopPropagation())})})},k=function(){try{var a=window.getSelection();if(a.getRangeAt&&a.rangeCount)return a.getRangeAt(0)}catch(b){}},l=function(){d=k()},m=function(){try{var a=window.getSelection();if(d){try{a.removeAllRanges()}catch(b){document.body.createTextRange().select(),document.selection.empty()}a.addRange(d)}}catch(c){}},n=function(c){g.focus(),a.each(c,function(c,d){/^image\//.test(d.type)?a.when(b(d)).done(function(a){i("insertimage",a)}).fail(function(a){e.fileUploadError("file-reader",a)}):e.fileUploadError("unsupported-file-type",d.type)})},o=function(a,b){m(),document.queryCommandSupported("hiliteColor")&&document.execCommand("hiliteColor",0,b||"transparent"),l(),a.data(e.selectionMarker,b)},p=function(b,c){b.find(f).click(function(){m(),g.focus(),i(a(this).data(c.commandRole)),l()}),b.find("[data-toggle=dropdown]").click(m);var d=!!window.navigator.msPointerEnabled||!!document.all&&!!document.addEventListener;b.find("input[type=text][data-"+c.commandRole+"]").on("webkitspeechchange change",function(){var b=this.value;this.value="",m(),b&&(g.focus(),i(a(this).data(c.commandRole),b)),l()}).on("focus",function(){if(!d){var b=a(this);b.data(c.selectionMarker)||(o(b,c.selectionColor),b.focus())}}).on("blur",function(){if(!d){var b=a(this);b.data(c.selectionMarker)&&o(b,!1)}}),b.find("input[type=file][data-"+c.commandRole+"]").change(function(){m(),"file"===this.type&&this.files&&this.files.length>0&&n(this.files),l(),this.value=""})},q=function(){g.on("dragenter dragover",!1).on("drop",function(a){var b=a.originalEvent.dataTransfer;a.stopPropagation(),a.preventDefault(),b&&b.files&&b.files.length>0&&n(b.files)})};return e=a.extend({},a.fn.wysiwyg.defaults,c),f="a[data-"+e.commandRole+"],button[data-"+e.commandRole+"],input[type=button][data-"+e.commandRole+"]",j(e.hotKeys),e.dragAndDropImages&&q(),p(a(e.toolbarSelector),e),g.attr("contenteditable",!0).on("mouseup keyup mouseout",function(){l(),h()}),a(window).bind("touchend",function(a){var b=g.is(a.target)||g.has(a.target).length>0,c=k(),d=c&&c.startContainer===c.endContainer&&c.startOffset===c.endOffset;(!d||b)&&(l(),h())}),this},a.fn.wysiwyg.defaults={hotKeys:{"ctrl+b meta+b":"bold","ctrl+i meta+i":"italic","ctrl+u meta+u":"underline","ctrl+z meta+z":"undo","ctrl+y meta+y meta+shift+z":"redo","ctrl+l meta+l":"justifyleft","ctrl+r meta+r":"justifyright","ctrl+e meta+e":"justifycenter","ctrl+j meta+j":"justifyfull","shift+tab":"outdent",tab:"indent"},toolbarSelector:"[data-role=editor-toolbar]",commandRole:"edit",activeToolbarClass:"btn-info",selectionMarker:"edit-focus-marker",selectionColor:"darkgrey",dragAndDropImages:!0,fileUploadError:function(a,b){console.log("File upload error",a,b)}}}(window.jQuery);
--------------------------------------------------------------------------------
/assets/js/common.js:
--------------------------------------------------------------------------------
1 | var MSG_OK = 0;
2 | var MSG_ERR = -1;
3 | var MSG_REDIRECT = -2;
4 |
5 | jQuery(function($){
6 |
7 | //ajax提交
8 | $('.ajax-form').on('submit', function() {
9 | var url = $('.ajax-form').attr('action') + "?_t=" + Math.random();
10 | $('.alert').addClass('hide');
11 | $('button[type="submit"]').attr('disabled', true);
12 | if ($('#editor')) {
13 | $("input[name='editor_content']").val($('#editor').html());
14 | }
15 | $.post(url, $(".ajax-form").serialize(), function (out) {
16 | if (out.status == MSG_OK) { // 成功
17 | if (out.redirect != "") {
18 | window.location.href = out.redirect;
19 | } else {
20 | window.location.reload();
21 | }
22 | } else if (out.status == MSG_REDIRECT) {
23 | window.location.href = out.redirect;
24 | } else if (out.status == MSG_ERR) {
25 | if ($('.alert')) {
26 | $('.alert').removeClass('hide');
27 | $('.alert').html(out.msg);
28 | } else {
29 | alert(out.msg);
30 | }
31 | $('button[type="submit"]').removeAttr('disabled');
32 | }
33 | });
34 | return false;
35 | });
36 |
37 | $.datepicker.regional['zh-CN'] = {
38 | closeText: '关闭',
39 | prevText: '<上月',
40 | nextText: '下月>',
41 | currentText: '今天',
42 | monthNames: ['一月','二月','三月','四月','五月','六月',
43 | '七月','八月','九月','十月','十一月','十二月'],
44 | monthNamesShort: ['一','二','三','四','五','六',
45 | '七','八','九','十','十一','十二'],
46 | dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'],
47 | dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'],
48 | dayNamesMin: ['日','一','二','三','四','五','六'],
49 | weekHeader: '周',
50 | dateFormat: 'yy-mm-dd',
51 | firstDay: 1,
52 | isRTL: false,
53 | showMonthAfterYear: true,
54 | yearSuffix: '年'};
55 | $.datepicker.setDefaults($.datepicker.regional['zh-CN']);
56 |
57 |
58 | /** 日历控件 **/
59 | $( "#start_date, #end_date" ).datepicker({
60 | showOtherMonths: true,
61 | selectOtherMonths: false,
62 | dateFormat: 'yy-mm-dd',
63 | changeYear:true,
64 | changeMonth:true
65 | });
66 |
67 | $(function () {
68 | $('[data-toggle="tooltip"]').tooltip()
69 | })
70 |
71 | /*$('.delete').click(function () {
72 | return confirm('确定要删除这条记录吗?');
73 | });*/
74 | $.widget("ui.dialog", $.extend({}, $.ui.dialog.prototype, {
75 | _title: function(title) {
76 | var $title = this.options.title || ' '
77 | if( ("title_html" in this.options) && this.options.title_html == true )
78 | title.html($title);
79 | else title.text($title);
80 | }
81 | }));
82 | $( ".delete_confirm" ).on('click', function(e) {
83 | var del_url = $(this).attr('href');
84 | e.preventDefault();
85 | $( "#dialog-confirm" ).removeClass('hide').dialog({
86 | resizable: false,
87 | width: '320',
88 | modal: true,
89 | title: "",
90 | title_html: true,
91 | buttons: [
92 | {
93 | html: "
确认",
94 | "class" : "btn btn-danger btn-sm",
95 | click: function() {
96 | $( this).dialog('close');
97 | window.location.href = del_url;
98 | }
99 | }
100 | ,
101 | {
102 | html: "
取消",
103 | "class" : "btn btn-sm",
104 | click: function() {
105 | $( this ).dialog( "close" );
106 | }
107 | }
108 | ]
109 | });
110 | });
111 |
112 |
113 | });
--------------------------------------------------------------------------------
/assets/js/html5shiv.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @preserve HTML5 Shiv 3.7.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
3 | */
4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.2",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="
",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b)}(this,document),Function.prototype.bind||(Function.prototype.bind=function(a){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var b=Array.prototype.slice.call(arguments,1),c=this,d=function(){},e=function(){return c.apply(this instanceof d&&a?this:a,b.concat(Array.prototype.slice.call(arguments)))};return d.prototype=this.prototype,e.prototype=new d,e});
--------------------------------------------------------------------------------
/assets/js/jquery.autosize.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Autosize 1.18.15
3 | license: MIT
4 | http://www.jacklmoore.com/autosize
5 | */
6 | !function(a){var b,c={className:"autosizejs",id:"autosizejs",append:"\n",callback:!1,resizeDelay:10,placeholder:!0},d='
',e=["fontFamily","fontSize","fontWeight","fontStyle","letterSpacing","textTransform","wordSpacing","textIndent","whiteSpace"],f=a(d).data("autosize",!0)[0];f.style.lineHeight="99px","99px"===a(f).css("lineHeight")&&e.push("lineHeight"),f.style.lineHeight="",a.fn.autosize=function(d){return this.length?(d=a.extend({},c,d||{}),f.parentNode!==document.body&&a(document.body).append(f),this.each(function(){function c(){var b,c=window.getComputedStyle?window.getComputedStyle(m,null):!1;c?(b=m.getBoundingClientRect().width,(0===b||"number"!=typeof b)&&(b=parseFloat(c.width)),a.each(["paddingLeft","paddingRight","borderLeftWidth","borderRightWidth"],function(a,d){b-=parseFloat(c[d])})):b=n.width(),f.style.width=Math.max(b,0)+"px"}function g(){var g={};if(b=m,f.className=d.className,f.id=d.id,j=parseFloat(n.css("maxHeight")),a.each(e,function(a,b){g[b]=n.css(b)}),a(f).css(g).attr("wrap",n.attr("wrap")),c(),window.chrome){var h=m.style.width;m.style.width="0px";{m.offsetWidth}m.style.width=h}}function h(){var a,e;b!==m?g():c(),f.value=!m.value&&d.placeholder?n.attr("placeholder")||"":m.value,f.value+=d.append||"",f.style.overflowY=m.style.overflowY,e=parseFloat(m.style.height),f.scrollTop=0,f.scrollTop=9e4,a=f.scrollTop,j&&a>j?(m.style.overflowY="scroll",a=j):(m.style.overflowY="hidden",k>a&&(a=k)),a+=o,e!==a&&(m.style.height=a+"px",f.className=f.className,p&&d.callback.call(m,m),n.trigger("autosize.resized"))}function i(){clearTimeout(l),l=setTimeout(function(){var a=n.width();a!==r&&(r=a,h())},parseInt(d.resizeDelay,10))}var j,k,l,m=this,n=a(m),o=0,p=a.isFunction(d.callback),q={height:m.style.height,overflow:m.style.overflow,overflowY:m.style.overflowY,wordWrap:m.style.wordWrap,resize:m.style.resize},r=n.width(),s=n.css("resize");n.data("autosize")||(n.data("autosize",!0),("border-box"===n.css("box-sizing")||"border-box"===n.css("-moz-box-sizing")||"border-box"===n.css("-webkit-box-sizing"))&&(o=n.outerHeight()-n.height()),k=Math.max(parseFloat(n.css("minHeight"))-o||0,n.height()),n.css({overflow:"hidden",overflowY:"hidden",wordWrap:"break-word"}),"vertical"===s?n.css("resize","none"):"both"===s&&n.css("resize","horizontal"),"onpropertychange"in m?"oninput"in m?n.on("input.autosize keyup.autosize",h):n.on("propertychange.autosize",function(){"value"===event.propertyName&&h()}):n.on("input.autosize",h),d.resizeDelay!==!1&&a(window).on("resize.autosize",i),n.on("autosize.resize",h),n.on("autosize.resizeIncludeStyle",function(){b=null,h()}),n.on("autosize.destroy",function(){b=null,clearTimeout(l),a(window).off("resize",i),n.off("autosize").off(".autosize").css(q).removeData("autosize")}),h())})):this}}(jQuery||$);
--------------------------------------------------------------------------------
/assets/js/jquery.dataTables.bootstrap.min.js:
--------------------------------------------------------------------------------
1 | $.extend(!0,$.fn.dataTable.defaults,{sDom:"<'row'<'col-xs-6'l><'col-xs-6'f>r>t<'row'<'col-xs-6'i><'col-xs-6'p>>",oLanguage:{sLengthMenu:"Display _MENU_ records"}}),$.extend($.fn.dataTableExt.oStdClasses,{sWrapper:"dataTables_wrapper form-inline",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm"}),$.fn.dataTable.Api?($.fn.dataTable.defaults.renderer="bootstrap",$.fn.dataTable.ext.renderer.pageButton.bootstrap=function(a,b,c,d,e,f){var g,h,i=new $.fn.dataTable.Api(a),j=a.oClasses,k=a.oLanguage.oPaginate,l=function(b,d){var m,n,o,p,q=function(a){a.preventDefault(),"ellipsis"!==a.data.action&&i.page(a.data.action).draw(!1)};for(m=0,n=d.length;n>m;m++)if(p=d[m],$.isArray(p))l(b,p);else{switch(g="",h="",p){case"ellipsis":g="…",h="disabled";break;case"first":g=k.sFirst,h=p+(e>0?"":" disabled");break;case"previous":g=k.sPrevious,h=p+(e>0?"":" disabled");break;case"next":g=k.sNext,h=p+(f-1>e?"":" disabled");break;case"last":g=k.sLast,h=p+(f-1>e?"":" disabled");break;default:g=p+1,h=e===p?"active":""}g&&(o=$("
",{"class":j.sPageButton+" "+h,"aria-controls":a.sTableId,tabindex:a.iTabIndex,id:0===c&&"string"==typeof p?a.sTableId+"_"+p:null}).append($("",{href:"#"}).html(g)).appendTo(b),a.oApi._fnBindAction(o,{action:p},q))}};l($(b).empty().html('').children("ul"),d)}):($.fn.dataTable.defaults.sPaginationType="bootstrap",$.fn.dataTableExt.oApi.fnPagingInfo=function(a){return{iStart:a._iDisplayStart,iEnd:a.fnDisplayEnd(),iLength:a._iDisplayLength,iTotal:a.fnRecordsTotal(),iFilteredTotal:a.fnRecordsDisplay(),iPage:-1===a._iDisplayLength?0:Math.ceil(a._iDisplayStart/a._iDisplayLength),iTotalPages:-1===a._iDisplayLength?0:Math.ceil(a.fnRecordsDisplay()/a._iDisplayLength)}},$.extend($.fn.dataTableExt.oPagination,{bootstrap:{fnInit:function(a,b,c){var d=(a.oLanguage.oPaginate,function(b){b.preventDefault(),a.oApi._fnPageChange(a,b.data.action)&&c(a)});$(b).append('');var e=$("a",b);$(e[0]).bind("click.DT",{action:"first"},d),$(e[1]).bind("click.DT",{action:"previous"},d),$(e[2]).bind("click.DT",{action:"next"},d),$(e[3]).bind("click.DT",{action:"last"},d)},fnUpdate:function(a,b){var c,d,e,f,g,h,i=5,j=a.oInstance.fnPagingInfo(),k=a.aanFeatures.p,l=Math.floor(i/2);for(j.iTotalPages=j.iTotalPages-l?(g=j.iTotalPages-i+1,h=j.iTotalPages):(g=j.iPage-l+1,h=g+i-1),c=0,d=k.length;d>c;c++){for($("li:gt(0)",k[c]).filter(":not(.next,.prev)").remove(),e=g;h>=e;e++)f=e==j.iPage+1?'class="active"':"",$("'+e+"").insertBefore($("li.next:eq(0)",k[c])[0]).bind("click",function(c){c.preventDefault(),a._iDisplayStart=(parseInt($("a",this).text(),10)-1)*j.iLength,b(a)});0===j.iPage?$("li.prev",k[c]).addClass("disabled"):$("li.prev",k[c]).removeClass("disabled"),j.iPage===j.iTotalPages-1||0===j.iTotalPages?$("li.next",k[c]).addClass("disabled"):$("li.next",k[c]).removeClass("disabled")}}}})),$.fn.DataTable.TableTools&&($.extend(!0,$.fn.DataTable.TableTools.classes,{container:"DTTT btn-group",buttons:{normal:"btn btn-default",disabled:"disabled"},collection:{container:"DTTT_dropdown dropdown-menu",buttons:{normal:"",disabled:"disabled"}},print:{info:"DTTT_print_info modal"},select:{row:"active"}}),$.extend(!0,$.fn.DataTable.TableTools.DEFAULTS.oTags,{collection:{container:"ul",button:"li",liner:"a"}}));
--------------------------------------------------------------------------------
/assets/js/jquery.easypiechart.min.js:
--------------------------------------------------------------------------------
1 | /**!
2 | * easyPieChart
3 | * Lightweight plugin to render simple, animated and retina optimized pie charts
4 | *
5 | * @license
6 | * @author Robert Fleischmann (http://robert-fleischmann.de)
7 | * @version 2.1.5
8 | **/
9 | !function(a,b){"object"==typeof exports?module.exports=b(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],b):b(a.jQuery)}(this,function(a){var b=function(a,b){var c,d=document.createElement("canvas");a.appendChild(d),"undefined"!=typeof G_vmlCanvasManager&&G_vmlCanvasManager.initElement(d);var e=d.getContext("2d");d.width=d.height=b.size;var f=1;window.devicePixelRatio>1&&(f=window.devicePixelRatio,d.style.width=d.style.height=[b.size,"px"].join(""),d.width=d.height=b.size*f,e.scale(f,f)),e.translate(b.size/2,b.size/2),e.rotate((-0.5+b.rotate/180)*Math.PI);var g=(b.size-b.lineWidth)/2;b.scaleColor&&b.scaleLength&&(g-=b.scaleLength+2),Date.now=Date.now||function(){return+new Date};var h=function(a,b,c){c=Math.min(Math.max(-1,c||0),1);var d=0>=c?!0:!1;e.beginPath(),e.arc(0,0,g,0,2*Math.PI*c,d),e.strokeStyle=a,e.lineWidth=b,e.stroke()},i=function(){var a,c;e.lineWidth=1,e.fillStyle=b.scaleColor,e.save();for(var d=24;d>0;--d)d%6===0?(c=b.scaleLength,a=0):(c=.6*b.scaleLength,a=b.scaleLength-c),e.fillRect(-b.size/2+a,0,c,1),e.rotate(Math.PI/12);e.restore()},j=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||function(a){window.setTimeout(a,1e3/60)}}(),k=function(){b.scaleColor&&i(),b.trackColor&&h(b.trackColor,b.lineWidth,1)};this.getCanvas=function(){return d},this.getCtx=function(){return e},this.clear=function(){e.clearRect(b.size/-2,b.size/-2,b.size,b.size)},this.draw=function(a){b.scaleColor||b.trackColor?e.getImageData&&e.putImageData?c?e.putImageData(c,0,0):(k(),c=e.getImageData(0,0,b.size*f,b.size*f)):(this.clear(),k()):this.clear(),e.lineCap=b.lineCap;var d;d="function"==typeof b.barColor?b.barColor(a):b.barColor,h(d,b.lineWidth,a/100)}.bind(this),this.animate=function(a,c){var d=Date.now();b.onStart(a,c);var e=function(){var f=Math.min(Date.now()-d,b.animate.duration),g=b.easing(this,f,a,c-a,b.animate.duration);this.draw(g),b.onStep(a,c,g),f>=b.animate.duration?b.onStop(a,c):j(e)}.bind(this);j(e)}.bind(this)},c=function(a,c){var d={barColor:"#ef1e25",trackColor:"#f9f9f9",scaleColor:"#dfe0e0",scaleLength:5,lineCap:"round",lineWidth:3,size:110,rotate:0,animate:{duration:1e3,enabled:!0},easing:function(a,b,c,d,e){return b/=e/2,1>b?d/2*b*b+c:-d/2*(--b*(b-2)-1)+c},onStart:function(){},onStep:function(){},onStop:function(){}};if("undefined"!=typeof b)d.renderer=b;else{if("undefined"==typeof SVGRenderer)throw new Error("Please load either the SVG- or the CanvasRenderer");d.renderer=SVGRenderer}var e={},f=0,g=function(){this.el=a,this.options=e;for(var b in d)d.hasOwnProperty(b)&&(e[b]=c&&"undefined"!=typeof c[b]?c[b]:d[b],"function"==typeof e[b]&&(e[b]=e[b].bind(this)));e.easing="string"==typeof e.easing&&"undefined"!=typeof jQuery&&jQuery.isFunction(jQuery.easing[e.easing])?jQuery.easing[e.easing]:d.easing,"number"==typeof e.animate&&(e.animate={duration:e.animate,enabled:!0}),"boolean"!=typeof e.animate||e.animate||(e.animate={duration:1e3,enabled:e.animate}),this.renderer=new e.renderer(a,e),this.renderer.draw(f),a.dataset&&a.dataset.percent?this.update(parseFloat(a.dataset.percent)):a.getAttribute&&a.getAttribute("data-percent")&&this.update(parseFloat(a.getAttribute("data-percent"))),a.style.width=a.style.height=e.size+"px",a.style.lineHeight=e.size-1+"px"}.bind(this);this.update=function(a){return a=parseFloat(a),e.animate.enabled?this.renderer.animate(f,a):this.renderer.draw(a),f=a,this}.bind(this),this.disableAnimation=function(){return e.animate.enabled=!1,this},this.enableAnimation=function(){return e.animate.enabled=!0,this},g()};a.fn.easyPieChart=function(b){return this.each(function(){var d;a.data(this,"easyPieChart")||(d=a.extend({},b,a(this).data()),a.data(this,"easyPieChart",new c(this,d)))})}});
--------------------------------------------------------------------------------
/assets/js/jquery.flot.resize.min.js:
--------------------------------------------------------------------------------
1 | !function(a,b,c){"$:nomunge";function d(c){h===!0&&(h=c||1);for(var i=f.length-1;i>=0;i--){var m=a(f[i]);if(m[0]==b||m.is(":visible")){var n=m.width(),o=m.height(),p=m.data(k);!p||n===p.w&&o===p.h||(m.trigger(j,[p.w=n,p.h=o]),h=c||!0)}else p=m.data(k),p.w=0,p.h=0}null!==e&&(h&&(null==c||1e3>c-h)?e=b.requestAnimationFrame(d):(e=setTimeout(d,g[l]),h=!1))}var e,f=[],g=a.resize=a.extend(a.resize,{}),h=!1,i="setTimeout",j="resize",k=j+"-special-event",l="pendingDelay",m="activeDelay",n="throttleWindow";g[l]=200,g[m]=20,g[n]=!0,a.event.special[j]={setup:function(){if(!g[n]&&this[i])return!1;var b=a(this);f.push(this),b.data(k,{w:b.width(),h:b.height()}),1===f.length&&(e=c,d())},teardown:function(){if(!g[n]&&this[i])return!1;for(var b=a(this),c=f.length-1;c>=0;c--)if(f[c]==this){f.splice(c,1);break}b.removeData(k),f.length||(h?cancelAnimationFrame(e):clearTimeout(e),e=null)},add:function(b){function d(b,d,f){var g=a(this),h=g.data(k)||{};h.w=d!==c?d:g.width(),h.h=f!==c?f:g.height(),e.apply(this,arguments)}if(!g[n]&&this[i])return!1;var e;return a.isFunction(b)?(e=b,d):(e=b.handler,void(b.handler=d))}},b.requestAnimationFrame||(b.requestAnimationFrame=function(){return b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame||b.oRequestAnimationFrame||b.msRequestAnimationFrame||function(a){return b.setTimeout(function(){a((new Date).getTime())},g[m])}}()),b.cancelAnimationFrame||(b.cancelAnimationFrame=function(){return b.webkitCancelRequestAnimationFrame||b.mozCancelRequestAnimationFrame||b.oCancelRequestAnimationFrame||b.msCancelRequestAnimationFrame||clearTimeout}())}(jQuery,this),function(a){function b(a){function b(){var b=a.getPlaceholder();0!=b.width()&&0!=b.height()&&(a.resize(),a.setupGrid(),a.draw())}function c(a){a.getPlaceholder().resize(b)}function d(a){a.getPlaceholder().unbind("resize",b)}a.hooks.bindEvents.push(c),a.hooks.shutdown.push(d)}var c={};a.plot.plugins.push({init:b,options:c,name:"resize",version:"1.0"})}(jQuery);
--------------------------------------------------------------------------------
/assets/js/jquery.gritter.min.js:
--------------------------------------------------------------------------------
1 | !function(a){a.gritter={},a.gritter.options={position:"",class_name:"",fade_in_speed:"medium",fade_out_speed:1e3,time:6e3},a.gritter.add=function(a){try{return b.add(a||{})}catch(c){var d="Gritter Error: "+c;"undefined"!=typeof console&&console.error?console.error(d,a):alert(d)}},a.gritter.remove=function(a,c){b.removeSpecific(a,c||{})},a.gritter.removeAll=function(a){b.stop(a||{})};var b={position:"",fade_in_speed:"",fade_out_speed:"",time:"",_custom_timer:0,_item_count:0,_is_setup:0,_tpl_close:'',_tpl_title:'[[title]]',_tpl_item:'',_tpl_wrap:'',add:function(c){if("string"==typeof c&&(c={text:c}),null===c.text)throw'You must supply "text" parameter.';this._is_setup||this._runSetup();var d=c.title,e=c.text,f=c.image||"",g=c.sticky||!1,h=c.class_name||a.gritter.options.class_name,i=a.gritter.options.position,j=c.time||"";this._verifyWrapper(),this._item_count++;var k=this._item_count,l=this._tpl_item;a(["before_open","after_open","before_close","after_close"]).each(function(d,e){b["_"+e+"_"+k]=a.isFunction(c[e])?c[e]:function(){}}),this._custom_timer=0,j&&(this._custom_timer=j);var m=""!=f?'
':"",n=""!=f?"gritter-with-image":"gritter-without-image";if(d=d?this._str_replace("[[title]]",d,this._tpl_title):"",l=this._str_replace(["[[title]]","[[text]]","[[close]]","[[image]]","[[number]]","[[class_name]]","[[item_class]]"],[d,e,this._tpl_close,m,this._item_count,n,h],l),this["_before_open_"+k]()===!1)return!1;a("#gritter-notice-wrapper").addClass(i).append(l);var o=a("#gritter-item-"+this._item_count);return o.fadeIn(this.fade_in_speed,function(){b["_after_open_"+k](a(this))}),g||this._setFadeTimer(o,k),a(o).bind("mouseenter mouseleave",function(c){"mouseenter"==c.type?g||b._restoreItemIfFading(a(this),k):g||b._setFadeTimer(a(this),k),b._hoverState(a(this),c.type)}),a(o).find(".gritter-close").click(function(){b.removeSpecific(k,{},null,!0)}),k},_countRemoveWrapper:function(b,c,d){c.remove(),this["_after_close_"+b](c,d),0==a(".gritter-item-wrapper").length&&a("#gritter-notice-wrapper").remove()},_fade:function(a,c,d,e){var d=d||{},f="undefined"!=typeof d.fade?d.fade:!0,g=d.speed||this.fade_out_speed,h=e;this["_before_close_"+c](a,h),e&&a.unbind("mouseenter mouseleave"),f?a.animate({opacity:0},g,function(){a.animate({height:0},300,function(){b._countRemoveWrapper(c,a,h)})}):this._countRemoveWrapper(c,a)},_hoverState:function(a,b){"mouseenter"==b?(a.addClass("hover"),a.find(".gritter-close").show()):(a.removeClass("hover"),a.find(".gritter-close").hide())},removeSpecific:function(b,c,d,e){if(!d)var d=a("#gritter-item-"+b);this._fade(d,b,c||{},e)},_restoreItemIfFading:function(a,b){clearTimeout(this["_int_id_"+b]),a.stop().css({opacity:"",height:""})},_runSetup:function(){for(opt in a.gritter.options)this[opt]=a.gritter.options[opt];this._is_setup=1},_setFadeTimer:function(a,c){var d=this._custom_timer?this._custom_timer:this.time;this["_int_id_"+c]=setTimeout(function(){b._fade(a,c)},d)},stop:function(b){var c=a.isFunction(b.before_close)?b.before_close:function(){},d=a.isFunction(b.after_close)?b.after_close:function(){},e=a("#gritter-notice-wrapper");c(e),e.fadeOut(function(){a(this).remove(),d()})},_str_replace:function(a,b,c,d){var e=0,f=0,g="",h="",i=0,j=0,k=[].concat(a),l=[].concat(b),m=c,n=l instanceof Array,o=m instanceof Array;for(m=[].concat(m),d&&(this.window[d]=0),e=0,i=m.length;i>e;e++)if(""!==m[e])for(f=0,j=k.length;j>f;f++)g=m[e]+"",h=n?void 0!==l[f]?l[f]:"":l[0],m[e]=g.split(k[f]).join(h),d&&m[e]!==g&&(this.window[d]+=(g.length-m[e].length)/k[f].length);return o?m:m[0]},_verifyWrapper:function(){0==a("#gritter-notice-wrapper").length&&a("body").append(this._tpl_wrap)}}}(jQuery);
--------------------------------------------------------------------------------
/assets/js/jquery.hotkeys.min.js:
--------------------------------------------------------------------------------
1 | !function(a){function b(b){if("string"==typeof b.data&&(b.data={keys:b.data}),b.data&&b.data.keys&&"string"==typeof b.data.keys){var c=b.handler,d=b.data.keys.toLowerCase().split(" ");b.handler=function(b){if(this===b.target||!(/textarea|select/i.test(b.target.nodeName)||a.hotkeys.options.filterTextInputs&&a.inArray(b.target.type,a.hotkeys.textAcceptingInputTypes)>-1)){var e="keypress"!==b.type&&a.hotkeys.specialKeys[b.which],f=String.fromCharCode(b.which).toLowerCase(),g="",h={};a.each(["alt","ctrl","shift"],function(a,c){b[c+"Key"]&&e!==c&&(g+=c+"+")}),b.metaKey&&!b.ctrlKey&&"meta"!==e&&(g+="meta+"),b.metaKey&&"meta"!==e&&g.indexOf("alt+ctrl+shift+")>-1&&(g=g.replace("alt+ctrl+shift+","hyper+")),e?h[g+e]=!0:(h[g+f]=!0,h[g+a.hotkeys.shiftNums[f]]=!0,"shift+"===g&&(h[a.hotkeys.shiftNums[f]]=!0));for(var i=0,j=d.length;j>i;i++)if(h[d[i]])return c.apply(this,arguments)}}}}a.hotkeys={version:"0.8",specialKeys:{8:"backspace",9:"tab",10:"return",13:"return",16:"shift",17:"ctrl",18:"alt",19:"pause",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",59:";",61:"=",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scroll",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},shiftNums:{"`":"~",1:"!",2:"@",3:"#",4:"$",5:"%",6:"^",7:"&",8:"*",9:"(",0:")","-":"_","=":"+",";":": ","'":'"',",":"<",".":">","/":"?","\\":"|"},textAcceptingInputTypes:["text","password","number","email","url","range","date","month","week","time","datetime","datetime-local","search","color","tel"],options:{filterTextInputs:!0}},a.each(["keydown","keyup","keypress"],function(){a.event.special[this]={add:b}})}(jQuery||this.jQuery||window.jQuery);
--------------------------------------------------------------------------------
/assets/js/jquery.inputlimiter.1.3.1.min.js:
--------------------------------------------------------------------------------
1 | !function(a){a.fn.inputlimiter=function(b){{var c=a.extend({},a.fn.inputlimiter.defaults,b);a(this)}c.boxAttach&&!a("#"+c.boxId).length&&(a("").appendTo("body").attr({id:c.boxId,"class":c.boxClass}).css({position:"absolute"}).hide(),a.fn.bgiframe&&a("#"+c.boxId).bgiframe());var d=function(){var b=a(this),d=g(b.val());!c.allowExceed&&d>c.limit&&b.val(h(b.val())),c.boxAttach&&a("#"+c.boxId).css({width:b.outerWidth()-(a("#"+c.boxId).outerWidth()-a("#"+c.boxId).width())+"px",left:b.offset().left+"px",top:b.offset().top+b.outerHeight()-1+"px","z-index":2e3});var e=c.limit-d>0?c.limit-d:0,f=c.remTextFilter(c,e),i=c.limitTextFilter(c);if(c.limitTextShow){a("#"+c.boxId).html(f+" "+i);var j=a("").appendTo("body").attr({id:"19cc9195583bfae1fad88e19d443be7a","class":c.boxClass}).html(f+" "+i).innerWidth();a("#19cc9195583bfae1fad88e19d443be7a").remove(),j>a("#"+c.boxId).innerWidth()&&a("#"+c.boxId).html(f+"
"+i),a("#"+c.boxId).show()}else a("#"+c.boxId).html(f).show()},e=function(b){var d=g(a(this).val());if(!c.allowExceed&&d>c.limit){var e=b.ctrlKey||b.altKey||b.metaKey;if(!e&&b.which>=32&&b.which<=122&&this.selectionStart===this.selectionEnd)return!1}},f=function(){var b=a(this);if(count=g(b.val()),!c.allowExceed&&count>c.limit&&b.val(h(b.val())),c.boxAttach)a("#"+c.boxId).fadeOut("fast");else if(c.remTextHideOnBlur){var d=c.limitText;d=d.replace(/\%n/g,c.limit),d=d.replace(/\%s/g,1===c.limit?"":"s"),a("#"+c.boxId).html(d)}},g=function(b){if("words"===c.limitBy.toLowerCase())return b.length>0?a.trim(b).replace(/\ +(?= )/g,"").split(" ").length:0;var d=b.length,e=b.match(/\n/g);return e&&c.lineReturnCount>1&&(d+=e.length*(c.lineReturnCount-1)),d},h=function(b){return"words"===c.limitBy.toLowerCase()?a.trim(b).replace(/\ +(?= )/g,"").split(" ").splice(0,c.limit).join(" ")+" ":b.substring(0,c.limit)};a(this).each(function(){var g=a(this);(!b||!b.limit)&&c.useMaxlength&&parseInt(g.attr("maxlength"))>0&&parseInt(g.attr("maxlength"))!=c.limit?g.inputlimiter(a.extend({},c,{limit:parseInt(g.attr("maxlength"))})):(!c.allowExceed&&c.useMaxlength&&"characters"===c.limitBy.toLowerCase()&&g.attr("maxlength",c.limit),g.unbind(".inputlimiter"),g.bind("keyup.inputlimiter",d),g.bind("keypress.inputlimiter",e),g.bind("blur.inputlimiter",f))})},a.fn.inputlimiter.remtextfilter=function(a,b){var c=a.remText;return 0===b&&null!==a.remFullText&&(c=a.remFullText),c=c.replace(/\%n/g,b),c=c.replace(/\%s/g,a.zeroPlural?1===b?"":"s":1>=b?"":"s")},a.fn.inputlimiter.limittextfilter=function(a){var b=a.limitText;return b=b.replace(/\%n/g,a.limit),b=b.replace(/\%s/g,a.limit<=1?"":"s")},a.fn.inputlimiter.defaults={limit:255,boxAttach:!0,boxId:"limiterBox",boxClass:"limiterBox",remText:"%n character%s remaining.",remTextFilter:a.fn.inputlimiter.remtextfilter,remTextHideOnBlur:!0,remFullText:null,limitTextShow:!0,limitText:"Field limited to %n character%s.",limitTextFilter:a.fn.inputlimiter.limittextfilter,zeroPlural:!0,allowExceed:!1,useMaxlength:!0,limitBy:"characters",lineReturnCount:1}}(jQuery);
--------------------------------------------------------------------------------
/assets/js/jquery.maskedinput.min.js:
--------------------------------------------------------------------------------
1 | !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){var b,c=navigator.userAgent,d=/iphone/i.test(c),e=/chrome/i.test(c),f=/android/i.test(c);a.mask={definitions:{9:"[0-9]",a:"[A-Za-z]","*":"[A-Za-z0-9]"},autoclear:!0,dataName:"rawMaskFn",placeholder:"_"},a.fn.extend({caret:function(a,b){var c;return 0===this.length||this.is(":hidden")?void 0:"number"==typeof a?(b="number"==typeof b?b:a,this.each(function(){this.setSelectionRange?this.setSelectionRange(a,b):this.createTextRange&&(c=this.createTextRange(),c.collapse(!0),c.moveEnd("character",b),c.moveStart("character",a),c.select())})):(this[0].setSelectionRange?(a=this[0].selectionStart,b=this[0].selectionEnd):document.selection&&document.selection.createRange&&(c=document.selection.createRange(),a=0-c.duplicate().moveStart("character",-1e5),b=a+c.text.length),{begin:a,end:b})},unmask:function(){return this.trigger("unmask")},mask:function(c,g){var h,i,j,k,l,m,n,o;if(!c&&this.length>0){h=a(this[0]);var p=h.data(a.mask.dataName);return p?p():void 0}return g=a.extend({autoclear:a.mask.autoclear,placeholder:a.mask.placeholder,completed:null},g),i=a.mask.definitions,j=[],k=n=c.length,l=null,a.each(c.split(""),function(a,b){"?"==b?(n--,k=a):i[b]?(j.push(new RegExp(i[b])),null===l&&(l=j.length-1),k>a&&(m=j.length-1)):j.push(null)}),this.trigger("unmask").each(function(){function h(){if(g.completed){for(var a=l;m>=a;a++)if(j[a]&&C[a]===p(a))return;g.completed.call(B)}}function p(a){return g.placeholder.charAt(a=0&&!j[a];);return a}function s(a,b){var c,d;if(!(0>a)){for(c=a,d=q(b);n>c;c++)if(j[c]){if(!(n>d&&j[c].test(C[d])))break;C[c]=C[d],C[d]=p(d),d=q(d)}z(),B.caret(Math.max(l,a))}}function t(a){var b,c,d,e;for(b=a,c=p(a);n>b;b++)if(j[b]){if(d=q(b),e=C[b],C[b]=c,!(n>d&&j[d].test(e)))break;c=e}}function u(){var a=B.val(),b=B.caret();if(a.length0&&!j[b.begin-1];)b.begin--;if(0===b.begin)for(;b.beging)&&g&&13!==g){if(i.end-i.begin!==0&&(y(i.begin,i.end),s(i.begin,i.end-1)),c=q(i.begin-1),n>c&&(d=String.fromCharCode(g),j[c].test(d))){if(t(c),C[c]=d,z(),e=q(c),f){var k=function(){a.proxy(a.fn.caret,B,e)()};setTimeout(k,0)}else B.caret(e);i.begin<=m&&h()}b.preventDefault()}}}function y(a,b){var c;for(c=a;b>c&&n>c;c++)j[c]&&(C[c]=p(c))}function z(){B.val(C.join(""))}function A(a){var b,c,d,e=B.val(),f=-1;for(b=0,d=0;n>b;b++)if(j[b]){for(C[b]=p(b);d++e.length){y(b+1,n);break}}else C[b]===e.charAt(d)&&d++,k>b&&(f=b);return a?z():k>f+1?g.autoclear||C.join("")===D?(B.val()&&B.val(""),y(0,n)):z():(z(),B.val(B.val().substring(0,f+1))),k?b:l}var B=a(this),C=a.map(c.split(""),function(a,b){return"?"!=a?i[a]?p(b):a:void 0}),D=C.join(""),E=B.val();B.data(a.mask.dataName,function(){return a.map(C,function(a,b){return j[b]&&a!=p(b)?a:null}).join("")}),B.one("unmask",function(){B.off(".mask").removeData(a.mask.dataName)}).on("focus.mask",function(){if(!B.prop("readonly")){clearTimeout(b);var a;E=B.val(),a=A(),b=setTimeout(function(){z(),a==c.replace("?","").length?B.caret(0,a):B.caret(a)},10)}}).on("blur.mask",v).on("keydown.mask",w).on("keypress.mask",x).on("input.mask paste.mask",function(){B.prop("readonly")||setTimeout(function(){var a=A(!0);B.caret(a),h()},0)}),e&&f&&B.off("input.mask").on("input.mask",u),A()})}})});
--------------------------------------------------------------------------------
/assets/js/jquery.ui.touch-punch.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery UI Touch Punch 0.2.3
3 | *
4 | * Copyright 2011–2014, Dave Furfero
5 | * Dual licensed under the MIT or GPL Version 2 licenses.
6 | *
7 | * Depends:
8 | * jquery.ui.widget.js
9 | * jquery.ui.mouse.js
10 | */
11 | !function(a){function b(a,b,c){if(!(a.originalEvent.touches.length>1)){c!==!1&&a.preventDefault();var d=a.originalEvent.changedTouches[0],e=document.createEvent("MouseEvents");e.initMouseEvent(b,!0,!0,window,1,d.screenX,d.screenY,d.clientX,d.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(e)}}if(a.support.touch="ontouchend"in document,a.support.touch){var c,d=a.ui.mouse.prototype,e=d._mouseInit,f=d._mouseDestroy;d._touchStart=function(a){var d=this;!c&&d._mouseCapture(a.originalEvent.changedTouches[0])&&(c=!0,d._touchMoved=!1,b(a,"mouseover",!1),b(a,"mousemove",!1),b(a,"mousedown",!1))},d._touchMove=function(a){c&&(this._touchMoved=!0,b(a,"mousemove"))},d._touchEnd=function(a){c&&(b(a,"mouseup"),b(a,"mouseout"),this._touchMoved||b(a,"click"),c=!1)},d._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),e.call(b)},d._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),f.call(b)}}}(jQuery);
--------------------------------------------------------------------------------
/assets/js/spin.min.js:
--------------------------------------------------------------------------------
1 | !function(a,b){"object"==typeof exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return l[e]||(m.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",m.cssRules.length),l[e]=1),e}function d(a,b){var c,d,e=a.style;for(b=b.charAt(0).toUpperCase()+b.slice(1),d=0;d',c)}m.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.width,left:d.radius,top:-d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.length+d.width,k=2*j,l=2*-(d.width+d.length)+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k /dev/null 2>&1 ; do
6 | echo "waiting for mysql startup..."
7 | sleep 3
8 | done
9 |
10 | exec /gopub/gopub
--------------------------------------------------------------------------------
/logs/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/logs/.gitkeep
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/astaxie/beego"
6 | "github.com/beego/i18n"
7 | "github.com/lisijie/gopub/app/controllers"
8 | _ "github.com/lisijie/gopub/app/mail"
9 | "github.com/lisijie/gopub/app/service"
10 | "time"
11 | )
12 |
13 | const VERSION = "2.0.1"
14 |
15 | func main() {
16 | service.Init()
17 |
18 | beego.AppConfig.Set("version", VERSION)
19 | if beego.AppConfig.String("runmode") == "dev" {
20 | beego.SetLevel(beego.LevelDebug)
21 | } else {
22 | beego.SetLevel(beego.LevelInformational)
23 | beego.SetLogger("file", `{"filename":"`+beego.AppConfig.String("log_file")+`"}`)
24 | beego.BeeLogger.DelLogger("console")
25 | }
26 |
27 | beego.Router("/", &controllers.MainController{}, "*:Index")
28 | beego.Router("/login", &controllers.MainController{}, "*:Login")
29 | beego.Router("/logout", &controllers.MainController{}, "*:Logout")
30 | beego.Router("/profile", &controllers.MainController{}, "*:Profile")
31 |
32 | beego.AutoRouter(&controllers.ProjectController{})
33 | beego.AutoRouter(&controllers.TaskController{})
34 | beego.AutoRouter(&controllers.ServerController{})
35 | beego.AutoRouter(&controllers.EnvController{})
36 | beego.AutoRouter(&controllers.UserController{})
37 | beego.AutoRouter(&controllers.RoleController{})
38 | beego.AutoRouter(&controllers.MailTplController{})
39 | beego.AutoRouter(&controllers.AgentController{})
40 | beego.AutoRouter(&controllers.ReviewController{})
41 | beego.AutoRouter(&controllers.MainController{})
42 |
43 | // 记录启动时间
44 | beego.AppConfig.Set("up_time", fmt.Sprintf("%d", time.Now().Unix()))
45 |
46 | beego.AddFuncMap("i18n", i18n.Tr)
47 |
48 | beego.SetStaticPath("/assets", "assets")
49 | beego.Run()
50 | }
51 |
--------------------------------------------------------------------------------
/pack.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | tarfile="gopub-$1-linux-amd64.tar.gz"
4 |
5 | echo "开始打包$tarfile..."
6 |
7 | export GOARCH=amd64
8 | export GOOS=linux
9 |
10 | bee pack -exs=".go:.DS_Store:.tmp:.log" -exr=data
11 |
12 | mv gopub.tar.gz $tarfile
13 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lisijie/gopub/bd53895a14babdc8588e3b116dca274a6df08a55/screenshot.png
--------------------------------------------------------------------------------
/service.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # -------------------------------------
3 | # 服务启动脚本
4 | #
5 | # @author jesse.li
6 | # @date 2016.05.13
7 | # -------------------------------------
8 |
9 | workDir=$(cd `dirname $0`; pwd)
10 | binFile="$workDir/gopub"
11 | pidFile="$workDir/gopub.pid"
12 | error=""
13 |
14 | cd $workDir
15 |
16 | start() {
17 | nohup $binFile > /dev/null 2>&1 &
18 | echo $! > $pidFile
19 | }
20 |
21 | stop() {
22 | if [[ -e $pidFile ]]; then
23 | pid=`cat $pidFile`
24 | rm -f $pidFile
25 | else
26 | pid=`ps aux | grep gopub | grep -v grep | awk '{print $2}' | head -1`
27 | fi
28 |
29 | if [ "$pid"x != ""x ]; then
30 | kill -9 $pid
31 | else
32 | error="服务不在运行状态"
33 | return 1
34 | fi
35 | }
36 |
37 | case $1 in
38 | start)
39 | if [[ -e $pidFile ]]; then
40 | echo "服务正在运行中, 进程ID: " $(cat $pidFile)
41 | exit 1
42 | fi
43 | echo -n "正在启动 ... "
44 | start
45 | sleep 1
46 | echo "成功, 进程ID:" $(cat $pidFile)
47 | ;;
48 | stop)
49 | echo -n "正在停止 ... "
50 | stop
51 | if [[ $? -gt 0 ]]; then
52 | echo "失败, ${error}"
53 | else
54 | echo "成功"
55 | fi
56 | ;;
57 | restart)
58 | echo -n "正在重启 ... "
59 | stop
60 | sleep 1
61 | start
62 | echo "成功, 进程ID:" $(cat $pidFile)
63 | ;;
64 | status)
65 | if [[ -e $pidFile ]]; then
66 | pid=$(cat $pidFile)
67 | else
68 | pid=`ps aux | grep gopub | grep -v grep | awk '{print $2}' | head -1`
69 | fi
70 | if [[ -z "$pid" ]]; then
71 | echo "服务不在运行状态"
72 | exit 1
73 | fi
74 | exists=$(ps -ef | grep $pid | grep -v grep | wc -l)
75 | if [[ $exists -gt 0 ]]; then
76 | echo "服务正在运行中, 进程ID为${pid}"
77 | else
78 | echo "服务不在运行状态, 但进程ID文件存在"
79 | fi
80 | ;;
81 | build)
82 | rebuild
83 | ;;
84 | *)
85 | echo "GoPub启动脚本"
86 | echo "用法: "
87 | echo " ./service.sh (start|stop|restart|status)"
88 | ;;
89 | esac
90 |
--------------------------------------------------------------------------------
/views/agent/add.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/views/agent/edit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/views/agent/list.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ID |
16 | 服务器IP |
17 | 机房 |
18 | SSH端口 |
19 | SSH用户 |
20 | 工作目录 |
21 | 操作 |
22 |
23 |
24 |
25 | {{range $k, $v := .list}}
26 |
27 | {{$v.Id}} |
28 | {{$v.Ip}} |
29 | {{$v.Area}} |
30 | {{$v.SshPort}} |
31 | {{$v.SshUser}} |
32 | {{$v.WorkDir}} |
33 |
34 | 项目列表 |
35 | 编辑 |
36 | 删除
37 | |
38 |
39 | {{end}}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
{{str2html .pageBar}}
47 |
48 |
--------------------------------------------------------------------------------
/views/agent/projects.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
服务器信息:
6 |
7 | - 服务器ID: {{.server.Id}}
8 | - 服务器IP: {{.server.Ip}}
9 | - 所在机房: {{.server.Area}}
10 |
11 |
12 |
13 |
14 |
15 |
16 | 项目列表:
17 |
18 |
19 |
20 |
21 |
22 | 项目ID |
23 | 项目名称 |
24 |
25 |
26 |
27 | {{range $k, $v := .list}}
28 |
29 | {{$v.projectId}} |
30 | {{$v.projectName}} |
31 |
32 | {{end}}
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/views/env/list.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ID |
16 | 环境名称 |
17 | SSH帐号 |
18 | SSH端口 |
19 | 发布目录 |
20 | 服务器数量 |
21 | 操作 |
22 |
23 |
24 |
25 | {{range $k, $v := .envList}}
26 |
27 | {{$v.Id}} |
28 | {{$v.Name}} |
29 | {{$v.SshUser}} |
30 | {{$v.SshPort}} |
31 | {{$v.PubDir}} |
32 | {{$v.ServerCount}} |
33 |
34 | 编辑 |
35 | 删除
36 | |
37 |
38 | {{end}}
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/views/error/message.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 出错了!
11 |
12 |
13 |
14 |
{{.msg}}
15 |
16 |
17 |
18 |
19 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/views/layout/layout.html:
--------------------------------------------------------------------------------
1 | {{.Header}}
2 |
3 | {{.Navbar}}
4 |
5 |
6 |
9 |
10 |
11 | {{.Sidebar}}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 | -
24 |
25 | 首页
26 |
27 | - {{.pageTitle}}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | {{.LayoutContent}}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {{.Footer}}
--------------------------------------------------------------------------------
/views/layout/sections/footer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 一旦删除将不可恢复,你确定吗?
5 |
6 |
7 |
8 |