前端重构#系统安装

develop
ouqiang 2018-05-07 21:57:19 +08:00
parent ac8936e238
commit 6606f64a4b
20 changed files with 596 additions and 49 deletions

View File

@ -1 +1,2 @@
.git
.git
nodu_modules

View File

@ -1,7 +1,5 @@
FROM alpine:latest
MAINTAINER ouqiang <qiangqianludao@gmail.com>
WORKDIR /usr/local/gocron
COPY . .

View File

@ -56,10 +56,12 @@
4. 浏览器访问 http://localhost:5920
### 源码安装
1. `go`语言版本1.9+
1. 安装Go 1.9+, Node.js, Yarn
2. `go get -d github.com/ouqiang/gocron`
3. 编译 `make`
4. 启动
3. 安装依赖 `make install-vue`
4. 前端打包 `make build-vue`
5. 编译Go代码 `make`
6. 启动
* gocron `./bin/gocron web`
* gocron-node `./bin/gocron-node`

View File

@ -11,11 +11,34 @@ type Setting struct {
Value string `xorm:"varchar(4096) notnull default '' "`
}
const slackTemplate = `
ID: {{.TaskId}}\n
: {{.TaskName}}\n
: \n {{.Status}} \n
: {{.Result}}
`
const emailTemplate = `
ID: {{.TaskId}}<br>
: {{.TaskName}}<br>
: \n {{.Status}}<br>
: {{.Result}}
`
const webhookTemplate = `
{
"task_id": "{{.TaskId}}",
"task_name": "{{.TaskName}}",
"status": "{{.Status}}",
"result": "{{.Result}}"
}
`
const SlackCode = "slack"
const SlackUrlKey = "url"
const SlackTemplateKey = "template"
const SlackChannelKey = "channel"
const MailCode = "mail"
const MailTemplateKey = "template"
const MailServerKey = "server"
const MailUserKey = "user"
@ -23,11 +46,25 @@ const MailUserKey = "user"
func (setting *Setting) InitBasicField() {
setting.Code = SlackCode
setting.Key = SlackUrlKey
setting.Value = ""
Db.Insert(setting)
setting.Id = 0
setting.Code = SlackCode
setting.Key = SlackTemplateKey
setting.Value = slackTemplate
Db.Insert(setting)
setting.Id = 0
setting.Code = MailCode
setting.Key = MailServerKey
setting.Value = ""
Db.Insert(setting)
setting.Id = 0
setting.Code = MailCode
setting.Key = MailTemplateKey
setting.Value = emailTemplate
Db.Insert(setting)
}
@ -36,6 +73,7 @@ func (setting *Setting) InitBasicField() {
type Slack struct {
Url string `json:"url"`
Channels []Channel `json:"channels"`
Template string `json:"template"`
}
type Channel struct {
@ -46,7 +84,7 @@ type Channel struct {
func (setting *Setting) Slack() (Slack, error) {
list := make([]Setting, 0)
err := Db.Where("code = ?", SlackCode).Find(&list)
slack := Slack{Url: "", Channels: make([]Channel, 0)}
slack := Slack{}
if err != nil {
return slack, err
}
@ -58,14 +96,16 @@ func (setting *Setting) Slack() (Slack, error) {
func (setting *Setting) formatSlack(list []Setting, slack *Slack) {
for _, v := range list {
if v.Key == SlackUrlKey {
switch v.Key {
case SlackUrlKey:
slack.Url = v.Value
continue
case SlackTemplateKey:
slack.Template = v.Value
default:
slack.Channels = append(slack.Channels, Channel{
v.Id, v.Value,
})
}
slack.Channels = append(slack.Channels, Channel{
v.Id, v.Value,
})
}
}
@ -111,6 +151,7 @@ type Mail struct {
User string `json:"user"`
Password string `json:"password"`
MailUsers []MailUser `json:"mail_users"`
Template string `json:"template"`
}
type MailUser struct {

View File

@ -130,8 +130,8 @@ func RegisterMiddleware(m *macaron.Macaron) {
m.Use(toolbox.Toolboxer(m))
}
m.Use(macaron.Renderer())
m.Use(ipAuth)
m.Use(checkAppInstall)
m.Use(ipAuth)
m.Use(userAuth)
m.Use(urlAuth)
}
@ -140,10 +140,10 @@ func RegisterMiddleware(m *macaron.Macaron) {
/** 检测应用是否已安装 **/
func checkAppInstall(ctx *macaron.Context) {
if ctx.Req.URL.Path == "/install" {
if app.Installed {
return
}
if app.Installed {
if ctx.Req.URL.Path == "/install/store" {
return
}
jsonResp := utils.JsonResponse{}
@ -154,6 +154,9 @@ func checkAppInstall(ctx *macaron.Context) {
// IP验证, 通过反向代理访问gocron需设置Header X-Real-IP才能获取到客户端真实IP
func ipAuth(ctx *macaron.Context) {
if !app.Installed {
return
}
allowIpsStr := app.Setting.AllowIps
if allowIpsStr == "" {
return
@ -173,6 +176,9 @@ func ipAuth(ctx *macaron.Context) {
// 用户认证
func userAuth(ctx *macaron.Context) {
if !app.Installed {
return
}
user.RestoreToken(ctx)
if user.IsLogin(ctx) {
return
@ -181,7 +187,7 @@ func userAuth(ctx *macaron.Context) {
if strings.HasPrefix(uri, "/v1") {
return
}
excludePaths := []string{"", "/install", "/user/login"}
excludePaths := []string{"", "/user/login"}
for _, path := range excludePaths {
if uri == path {
return
@ -195,6 +201,9 @@ func userAuth(ctx *macaron.Context) {
// URL权限验证
func urlAuth(ctx *macaron.Context) {
if !app.Installed {
return
}
if user.IsAdmin(ctx) {
return
}
@ -225,6 +234,9 @@ func urlAuth(ctx *macaron.Context) {
/** API接口签名验证 **/
func apiAuth(ctx *macaron.Context) {
if !app.Installed {
return
}
if !app.Setting.ApiSignEnable {
return
}

View File

@ -46,6 +46,10 @@ build-vue:
cd web/vue && yarn run build
cp -r web/vue/dist/ web/public/
.PHONY: install-vue
install-vue:
cp web/vue && yarn install
.PHONY: clean
clean:
rm bin/gocron

View File

@ -5,7 +5,7 @@
<app-nav-menu></app-nav-menu>
</el-header>
<el-main >
<div>
<div id="main-container">
<router-view/>
</div>
</el-main>
@ -35,9 +35,26 @@ export default {
}
</script>
<style>
body {
margin:0;
}
.el-header {
padding:0;
margin:0;
}
.el-container {
padding:0;
margin:0;
width: 100%;
}
.el-main {
padding:0;
margin:0;
}
#main-container .el-main {
margin:20px 20px 0 20px;
}
.el-aside .el-menu {
height: 100%;
}
</style>

View File

@ -7,15 +7,28 @@
text-color="#fff"
active-text-color="#ffd04b"
router>
<el-menu-item index="/task">任务管理</el-menu-item>
<el-menu-item index="/host">任务节点</el-menu-item>
<el-menu-item v-if="this.$store.getters.user.isAdmin" index="/user"></el-menu-item>
<el-menu-item v-if="this.$store.getters.user.isAdmin" index="/system"></el-menu-item>
<el-submenu v-if="this.$store.getters.user.token" index="userStatus">
<template slot="title">{{this.$store.getters.user.username}}</template>
<el-menu-item index="/user/edit-my-password">修改密码</el-menu-item>
<el-menu-item @click="logout" index="/user/logout">退出</el-menu-item>
</el-submenu>
<el-row>
<el-col :span="2">
<el-menu-item index="/task">任务管理</el-menu-item>
</el-col>
<el-col :span="2">
<el-menu-item index="/host">任务节点</el-menu-item>
</el-col>
<el-col :span="2">
<el-menu-item v-if="this.$store.getters.user.isAdmin" index="/user"></el-menu-item>
</el-col>
<el-col :span="2">
<el-menu-item v-if="this.$store.getters.user.isAdmin" index="/system"></el-menu-item>
</el-col>
<el-col :span="16"></el-col>
<el-col :span="2" style="float:right;">
<el-submenu v-if="this.$store.getters.user.token" index="userStatus">
<template slot="title">{{this.$store.getters.user.username}}</template>
<el-menu-item index="/user/edit-my-password">修改密码</el-menu-item>
<el-menu-item @click="logout" index="/user/logout">退出</el-menu-item>
</el-submenu>
</el-col>
</el-row>
</el-menu>
</div>
</template>

View File

@ -10,10 +10,6 @@ import store from './store/index'
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.prototype.$refresh = function () {
router.go(0)
}
Vue.prototype.$appConfirm = function (callback) {
this.$confirm('?', '', {
confirmButtonText: '',

View File

@ -14,8 +14,14 @@
</el-form-item>
</el-row>
</el-form>
<el-button type="success" v-if="this.$store.getters.user.isAdmin" @click="toEdit(null)"></el-button>
<el-button type="info" @click="refresh"></el-button> <br><br>
<el-row type="flex" justify="end">
<el-col :span="2">
<el-button type="primary" v-if="this.$store.getters.user.isAdmin" @click="toEdit(null)"></el-button>
</el-col>
<el-col :span="2">
<el-button type="info" @click="refresh"></el-button>
</el-col>
</el-row>
<el-pagination
background
layout="prev, pager, next, sizes, total"

View File

@ -1,14 +1,160 @@
<template>
<div>
安装页面
</div>
<el-container>
<el-main>
<el-form ref="form" :model="form" :rules="formRules" label-width="100px" style="width: 700px;">
<h3>数据库配置</h3>
<el-form-item label="数据库选择" prop="db_type">
<el-select v-model.trim="form.db_type">
<el-option
v-for="item in dbList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="主机名" prop="db_host">
<el-input v-model="form.db_host"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="端口" prop="db_port">
<el-input v-model.number="form.db_port"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="用户名" prop="db_username">
<el-input v-model="form.db_username"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="密码" prop="db_password">
<el-input v-model="form.db_password" type="password"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="数据库名称" prop="db_name">
<el-input v-model="form.db_name" placeholder="如果数据库不存在, 需提前创建"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="表前缀" prop="db_table_prefix">
<el-input v-model="form.db_table_prefix"></el-input>
</el-form-item>
</el-col>
</el-row>
<h3>管理员账号配置</h3>
<el-row>
<el-col :span="12">
<el-form-item label="账号" prop="admin_username">
<el-input v-model="form.admin_username"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱" prop="admin_email">
<el-input v-model="form.admin_email"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="密码" prop="admin_password">
<el-input v-model="form.admin_password" type="password"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="确认密码" prop="confirm_admin_password">
<el-input v-model="form.confirm_admin_password" type="password"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item>
<el-button type="primary" @click="submit()"></el-button>
</el-form-item>
</el-form>
</el-main>
</el-container>
</template>
<script>
import installService from '../../api/install'
export default {
name: 'index',
data () {
return {}
return {
form: {
db_type: 'mysql',
db_host: '127.0.0.1',
db_port: 3306,
db_username: '',
db_password: '',
db_name: '',
db_table_prefix: '',
admin_username: '',
admin_password: '',
confirm_admin_password: '',
admin_email: ''
},
formRules: {
db_type: [
{required: true, message: '请选择数据库', trigger: 'blur'}
],
db_host: [
{required: true, message: '请输入数据库主机名', trigger: 'blur'}
],
db_port: [
{type: 'number', required: true, message: '请输入数据库端口', trigger: 'blur'}
],
db_username: [
{required: true, message: '请输入数据库用户名', trigger: 'blur'}
],
db_password: [
{required: true, message: '请输入数据库密码', trigger: 'blur'}
],
db_name: [
{required: true, message: '请输入数据库名称', trigger: 'blur'}
],
admin_username: [
{required: true, message: '请输入管理员账号', trigger: 'blur'}
],
admin_email: [
{type: 'email', required: true, message: '请输入管理员邮箱', trigger: 'blur'}
],
admin_password: [
{required: true, message: '请输入管理员密码', trigger: 'blur'}
],
confirm_admin_password: [
{required: true, message: '请再次输入管理员密码', trigger: 'blur'}
]
},
dbList: [
{
value: 'mysql',
label: 'MySQL'
}
]
}
},
methods: {
submit () {
this.$refs['form'].validate((valid) => {
if (!valid) {
return false
}
this.save()
})
},
save () {
installService.store(this.form, () => {
this.$router.push('/')
})
}
}
}
</script>

View File

@ -0,0 +1,83 @@
<template>
<el-container>
<system-sidebar></system-sidebar>
<el-main>
<notification-tab></notification-tab>
<el-form ref="form" :model="form" label-width="100px" style="width: 700px;">
<h3>邮件服务器配置</h3>
<el-row>
<el-col :span="12">
<el-form-item label="SMTP服务器">
<el-input v-model="form.host"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="端口">
<el-input v-model="form.port"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="用户名">
<el-input v-model="form.user"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="密码">
<el-input v-model="form.password"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="模板">
<el-input
type="textarea"
:rows="5"
placeholder=""
v-model="form.template">
</el-input>
</el-form-item>
<h3>邮箱用户</h3>
<el-tag
v-for="item in form.receivers"
:key="item.email"
closable>
{{item.username}}
</el-tag>
<el-form-item>
<el-button type="primary" @click="submit()"></el-button>
</el-form-item>
</el-form>
</el-main>
</el-container>
</template>
<script>
import systemSidebar from '../sidebar'
import notificationTab from './tab'
export default {
name: 'notification-email',
data () {
return {
form: {
host: '',
port: 465,
user: '',
password: '',
template: '',
receivers: [
{
username: '欧强',
email: 'qingqianludao@gmail.com'
}
]
}
}
},
components: {notificationTab, systemSidebar},
methods: {
submit () {}
}
}
</script>

View File

@ -0,0 +1,54 @@
<template>
<el-container>
<system-sidebar></system-sidebar>
<el-main>
<notification-tab></notification-tab>
<el-form ref="form" :model="form" label-width="180px" style="width: 700px;">
<el-form-item label="Slack Webhook URL">
<el-input v-model="form.url"></el-input>
</el-form-item>
<el-form-item label="模板">
<el-input
type="textarea"
:rows="5"
placeholder=""
v-model="form.template">
</el-input>
</el-form-item>
<h3>邮箱用户</h3>
<el-tag
v-for="item in form.channels"
:key="item"
closable>
{{item}}
</el-tag>
<el-form-item>
<el-button type="primary" @click="submit()"></el-button>
</el-form-item>
</el-form>
</el-main>
</el-container>
</template>
<script>
import systemSidebar from '../sidebar'
import notificationTab from './tab'
export default {
name: 'notification-slack',
data () {
return {
form: {
url: '',
template: '',
channels: ['xiamen', 'taiwan']
}
}
},
components: {notificationTab, systemSidebar},
methods: {
submit () {
}
}
}
</script>

View File

@ -0,0 +1,30 @@
<template>
<el-tabs v-model="activeName" @tab-click="changeTab">
<el-tab-pane label="邮件" name="email"></el-tab-pane>
<el-tab-pane label="Slack" name="slack"></el-tab-pane>
<el-tab-pane label="Webhook" name="webhook"></el-tab-pane>
</el-tabs>
</template>
<script>
export default {
name: 'notification-tab',
data () {
return {
activeName: ''
}
},
created () {
const segments = this.$route.path.split('/')
if (segments.length !== 4) {
return 'email'
}
this.activeName = segments[3]
},
methods: {
changeTab (item) {
this.$router.push(`/system/notification/${item.name}`)
}
}
}
</script>

View File

@ -0,0 +1,46 @@
<template>
<el-container>
<system-sidebar></system-sidebar>
<el-main>
<notification-tab></notification-tab>
<el-form ref="form" :model="form" label-width="100px" style="width: 700px;">
<el-form-item label="URL">
<el-input v-model="form.url"></el-input>
</el-form-item>
<el-form-item label="模板">
<el-input
type="textarea"
:rows="5"
placeholder=""
v-model="form.template">
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit()"></el-button>
</el-form-item>
</el-form>
</el-main>
</el-container>
</template>
<script>
import systemSidebar from '../sidebar'
import notificationTab from './tab'
export default {
name: 'notification-webhook',
data () {
return {
form: {
url: '',
template: ''
}
}
},
components: {notificationTab, systemSidebar},
methods: {
submit () {
}
}
}
</script>

View File

@ -0,0 +1,82 @@
<template>
<el-container>
<task-sidebar></task-sidebar>
<el-main>
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="任务名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="活动区域">
<el-select v-model="form.region" placeholder="请选择活动区域">
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
<el-form-item label="活动时间">
<el-col :span="11">
<el-date-picker type="date" placeholder="选择日期" v-model="form.date1" style="width: 100%;"></el-date-picker>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-time-picker type="fixed-time" placeholder="选择时间" v-model="form.date2" style="width: 100%;"></el-time-picker>
</el-col>
</el-form-item>
<el-form-item label="即时配送">
<el-switch v-model="form.delivery"></el-switch>
</el-form-item>
<el-form-item label="活动性质">
<el-checkbox-group v-model="form.type">
<el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
<el-checkbox label="地推活动" name="type"></el-checkbox>
<el-checkbox label="线下主题活动" name="type"></el-checkbox>
<el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="特殊资源">
<el-radio-group v-model="form.resource">
<el-radio label="线上品牌商赞助"></el-radio>
<el-radio label="线下场地免费"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="活动形式">
<el-input type="textarea" v-model="form.desc"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit"></el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</el-main>
</el-container>
</template>
<script>
export default {
name: 'task-edit',
data () {
return {
form: {
id: '',
name: '',
tag: '',
level: 1,
dependency_status: 1,
dependency_task_id: '',
spec: '',
protocol: 2,
http_method: 1,
command: '',
host_id: '',
timeout: 0,
multi: 2,
notify_status: 1,
notify_type: 1,
notify_receive_id: '',
retry_times: 0,
retry_interval: 0,
remark: ''
}
}
}
}
</script>

View File

@ -53,8 +53,14 @@
</el-form-item>
</el-row>
</el-form>
<el-button type="success" v-if="this.$store.getters.user.isAdmin"></el-button>
<el-button type="info" @click="refresh"></el-button> <br> <br>
<el-row type="flex" justify="end">
<el-col :span="2">
<el-button type="primary" v-if="this.$store.getters.user.isAdmin"></el-button>
</el-col>
<el-col :span="2">
<el-button type="info" @click="refresh"></el-button>
</el-col>
</el-row>
<el-pagination
background
layout="prev, pager, next, sizes, total"

View File

@ -32,10 +32,14 @@
<el-button type="primary" @click="search"></el-button>
</el-form-item>
</el-form>
<p>
<el-button type="danger" v-if="this.$store.getters.user.isAdmin" @click="clearLog"></el-button>
<el-button type="info" @click="refresh"></el-button> <br> <br>
</p>
<el-row type="flex" justify="end">
<el-col :span="3">
<el-button type="danger" v-if="this.$store.getters.user.isAdmin" @click="clearLog"></el-button>
</el-col>
<el-col :span="2">
<el-button type="info" @click="refresh"></el-button>
</el-col>
</el-row>
<el-pagination
background
layout="prev, pager, next, sizes, total"
@ -72,7 +76,8 @@
</el-table-column>
<el-table-column
prop="name"
label="任务名称">
label="任务名称"
width="180">
</el-table-column>
<el-table-column
prop="protocol"

View File

@ -55,7 +55,6 @@ export default {
})
},
save () {
console.log(this.form)
userService.editPassword(this.form, () => {
this.$router.push('/user')
})

View File

@ -1,8 +1,14 @@
<template>
<el-container>
<el-main>
<el-button type="success" @click="toEdit(null)"></el-button>
<el-button type="info" @click="refresh"></el-button> <br><br>
<el-row type="flex" justify="end">
<el-col :span="2">
<el-button type="primary" @click="toEdit(null)"></el-button>
</el-col>
<el-col :span="2">
<el-button type="info" @click="refresh"></el-button>
</el-col>
</el-row>
<el-pagination
background
layout="prev, pager, next, sizes, total"