mirror of https://github.com/shunfei/cronsun
添加rest client,完善一些组件
parent
4460c5f4c1
commit
3f293a91ab
|
@ -1,10 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div class="ui purple inverted menu fixed">
|
<div class="ui blue inverted menu fixed">
|
||||||
<router-link class="active item" to="/">仪表盘</router-link>
|
<div class="item">CRONSUN</div>
|
||||||
<router-link class="item" to="/log">日志</router-link>
|
<router-link class="item" to="/" v-bind:class="{active: this.$route.path == '/'}"><i class="dashboard icon"></i> 仪表盘</router-link>
|
||||||
<router-link class="item" to="/job">任务</router-link>
|
<router-link class="item" to="/log" v-bind:class="{active: this.$route.path.indexOf('/log') === 0}"><i class="file text icon"></i> 日志</router-link>
|
||||||
<router-link class="item" to="/node">节点</router-link>
|
<router-link class="item" to="/job" v-bind:class="{active: this.$route.path.indexOf('/job') === 0}"><i class="calendar icon"></i> 任务</router-link>
|
||||||
|
<router-link class="item" to="/node" v-bind:class="{active: this.$route.path.indexOf('/node') === 0}"><i class="tasks icon"></i> 节点</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div style="height: 55px;"></div>
|
<div style="height: 55px;"></div>
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Vue from 'vue';
|
|
||||||
import JobToolbar from './JobToolbar.vue';
|
import JobToolbar from './JobToolbar.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'job',
|
name: 'job',
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -1,65 +1,123 @@
|
||||||
<template>
|
<template>
|
||||||
<form class="ui form segment" v-on:submit.preven>
|
<form class="ui form segment" v-bind:class="{loading:loading}" v-on:submit.prevent>
|
||||||
<h3 class="ui header">新建任务</h3>
|
<h3 class="ui header">{{action == 'CREATE' ? '添加' : '更新'}}任务
|
||||||
|
<div class="ui toggle checkbox">
|
||||||
|
<input type="checkbox" class="hidden" v-bind:checked="!job.pause">
|
||||||
|
<label v-bind:style="{color: (job.pause?'red':'green')+' !important'}">{{job.pause ? '任务已暂停' : '开启'}}</label>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
<div class="two fields">
|
<div class="two fields">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>任务名称</label>
|
<label>任务名称</label>
|
||||||
<input type="text" v-model="name" placeholder="任务名称">
|
<input type="text" ref="name" v-bind:value="job.name" v-on:input="updateValue($event.target.value)" placeholder="任务名称">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>任务分组</label>
|
<label>任务分组</label>
|
||||||
<div class="ui dropdown selection">
|
<Dropdown title="选择分组" allowAdditions=true v-bind:items="groups" v-bind:selected="job.group" v-on:change="changeGroup"/>
|
||||||
<input type="hidden">
|
|
||||||
<div class="default text">选择分组</div>
|
|
||||||
<i class="dropdown icon"></i>
|
|
||||||
<div class="menu">
|
|
||||||
<div class="item" data-value="default">Default</div>
|
|
||||||
<div class="item" data-value="ssp">SSP</div>
|
|
||||||
<div class="item" data-value="dc">数据中心</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>任务脚本</label>
|
<label>任务脚本</label>
|
||||||
<input type="text" v-model="cmd" placeholder="任务脚本">
|
<input type="text" v-model="job.cmd" placeholder="任务脚本">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui toggle checkbox">
|
<span v-if="job.rules.length == 0"><i class="warning circle icon"></i>当前任务没有定时器,点击下面按钮来添加定时器</span>
|
||||||
<input type="checkbox" class="hidden" v-bind:checked="!pause">
|
</div>
|
||||||
<label>{{pause ? '暂停' : '开启'}}</label>
|
<JobEditRule v-for="(rule, index) in job.rules" :rule="rule" :index="index" v-on:remove="removeRule" v-on:change="changeRule"/>
|
||||||
|
<div class="two fields">
|
||||||
|
<div class="field">
|
||||||
|
<button class="fluid ui button" v-on:click="addNewTimer" type="button"><i class="history icon"></i> 添加定时器</button>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<button class="fluid blue ui button" type="button" v-on:click="submit"><i class="upload icon"></i> 保存任务</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="fluid blue ui button" type="submit">创建</button>
|
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import JobEditRule from './JobEditRule.vue';
|
||||||
|
import Dropdown from './basic/Dropdown.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'job-edit',
|
name: 'job-edit',
|
||||||
data: function(){
|
data: function(){
|
||||||
return {
|
return {
|
||||||
name: '',
|
action: 'CREATE',
|
||||||
group: 'default',
|
groups: [],
|
||||||
cmd: '',
|
loading: false,
|
||||||
pause: false
|
job: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
group: 'default',
|
||||||
|
cmd: '',
|
||||||
|
pause: false,
|
||||||
|
rules: []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
updateValue: function(v){
|
||||||
|
var tv = v.replace(/[\*\/]/g, '');
|
||||||
|
this.job.name = tv;
|
||||||
|
if (tv !== v) {
|
||||||
|
this.$refs.name.value = tv;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addNewTimer: function(){
|
||||||
|
this.job.rules.push({});
|
||||||
|
},
|
||||||
|
|
||||||
|
changeGroup: function(val, text){
|
||||||
|
this.job.group = val;
|
||||||
|
},
|
||||||
|
|
||||||
|
removeRule: function(index){
|
||||||
|
this.job.rules.splice(index, 1);
|
||||||
|
},
|
||||||
|
changeRule: function(index, key, val){
|
||||||
|
this.job.rules[index][key] = val;
|
||||||
|
},
|
||||||
|
submit: function(){
|
||||||
|
var exceptCode = this.action == 'CREATE' ? 201 : 200;
|
||||||
|
this.loading = true;
|
||||||
|
var vm = this;
|
||||||
|
this.$rest.PUT('job', JSON.stringify(this.job))
|
||||||
|
.onsucceed(exceptCode, ()=>{vm.$router.push('/job')})
|
||||||
|
.onfailed((resp)=>{console.log(resp)})
|
||||||
|
.onend(()=>{vm.loading=false})
|
||||||
|
.do();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
mounted: function(){
|
mounted: function(){
|
||||||
|
this.action = this.$route.path.indexOf('/job/create') === 0 ? 'CREATE' : 'UPDATE';
|
||||||
|
|
||||||
var vm = this;
|
var vm = this;
|
||||||
|
this.$rest.GET('job/groups').onsucceed(200, (resp)=>{
|
||||||
|
!resp.includes('default') && resp.unshift('default');
|
||||||
|
vm.groups = resp;
|
||||||
|
}).do();
|
||||||
|
|
||||||
$(this.$el).find('.checkbox').checkbox({
|
$(this.$el).find('.checkbox').checkbox({
|
||||||
onChange: function(){
|
onChange: function(){
|
||||||
vm.pause = !vm.pause;
|
vm.job.pause = !vm.job.pause;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(this.$el).find('.dropdown').dropdown({
|
$(this.$el).find('.dropdown').dropdown({
|
||||||
allowAdditions: true,
|
allowAdditions: true,
|
||||||
onChange: function(value, text, $choice){
|
onChange: function(value, text, $choice){
|
||||||
vm.group = value;
|
vm.job.group = value;
|
||||||
}
|
}
|
||||||
}).dropdown('set selected', this.group);
|
}).dropdown('set selected', this.job.group);
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
JobEditRule,
|
||||||
|
Dropdown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -1,9 +1,61 @@
|
||||||
<template>
|
<template>
|
||||||
<router-link class="ui button" to="/job/create">新建任务</router-link>
|
<div style="margin-bottom:20px;">
|
||||||
|
<h4 class="ui horizontal divider header">定时器 - {{index}} <a href="#" v-on:click.prevent="remove">删除</a></h4>
|
||||||
|
<div class="two fields">
|
||||||
|
<div class="field">
|
||||||
|
<input type="text" v-bind:value="rule.timer" v-on:input="change('timer', $event.target.value)" placeholder="定时 * * * * *(crontab 格式)"/>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<Dropdown title="节点分组" v-bind:items="nodeGroups" multiple="true"></Dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label><strong style="color:green;">+</strong> 同时在这些节点上面运行任务</label>
|
||||||
|
<Dropdown title="选择节点" v-bind:items="activityNodes" multiple="true"></Dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label><strong style="color:red;">-</strong> 不在这些节点上面运行任务</label>
|
||||||
|
<Dropdown title="选择节点" v-bind:items="activityNodes" multiple="true"></Dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Dropdown from './basic/Dropdown.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'job-edit-rule'
|
name: 'job-edit-rule',
|
||||||
|
props: ['rule', 'index'],
|
||||||
|
data: function(){
|
||||||
|
return {
|
||||||
|
nodeGroups: [],
|
||||||
|
activityNodes: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted: function(){
|
||||||
|
var vm = this;
|
||||||
|
this.$rest.GET('node/activitys').onsucceed(200, (resp)=>{vm.activityNodes = resp}).do();
|
||||||
|
this.$rest.GET('node/groups').onsucceed(200, (resp)=>{
|
||||||
|
var groups = [];
|
||||||
|
for (var i in resp) {
|
||||||
|
groups.push({value: resp[i].id, name: resp[i].name});
|
||||||
|
}
|
||||||
|
vm.nodeGroups = groups;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
remove: function(){
|
||||||
|
this.$emit('remove', this.index);
|
||||||
|
},
|
||||||
|
change: function(key, val){
|
||||||
|
this.$emit('change', this.index, key, val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
Dropdown
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<router-link class="ui button" to="/job/create">新建任务</router-link>
|
<router-link class="ui button" to="/job/create"><i class="add to calendar icon"></i> 新任务</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<template>
|
||||||
|
<div class="ui search selection dropdown" v-bind:class="{multiple: multiple}">
|
||||||
|
<input type="hidden">
|
||||||
|
<div class="default text">{{title}}</div>
|
||||||
|
<i class="dropdown icon"></i>
|
||||||
|
<div class="menu">
|
||||||
|
<div class="item" v-for="item in items" v-bind:data-value="typeof item === 'object' ? item.value : item">{{typeof item === 'object' ? item.name : item}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'dropdown',
|
||||||
|
props: ['title', 'items', 'selected', 'allowAdditions', 'multiple'],
|
||||||
|
|
||||||
|
mounted: function() {
|
||||||
|
if (this.title.length === 0) this.title = '选择分组';
|
||||||
|
|
||||||
|
var vm = this;
|
||||||
|
$(this.$el).dropdown({
|
||||||
|
allowAdditions: !!this.allowAdditions,
|
||||||
|
hideAdditions: false,
|
||||||
|
forceSelection: false,
|
||||||
|
onChange: function(value, text, $choice){
|
||||||
|
vm.$emit('change', value, text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.selected !== null) {
|
||||||
|
$(this.$el).dropdown('set selected', this.selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,115 @@
|
||||||
|
var sendXHR = function(opt) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open(opt.method, opt.url, true);
|
||||||
|
|
||||||
|
if (typeof opt.onexception == 'function') {
|
||||||
|
var warpExceptionHandler = ()=>{
|
||||||
|
opt.onexception(msg);
|
||||||
|
typeof opt.onend == 'function' && opt.onend(xhr);
|
||||||
|
}
|
||||||
|
xhr.onabort=()=>{warpExceptionHandler('request aborted.')};
|
||||||
|
xhr.onerror=()=>{warpExceptionHandler('request error.')};
|
||||||
|
xhr.ontimeout=()=>{warpExceptionHandler('request timeout.')};
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function(){
|
||||||
|
if (xhr.readyState !== XMLHttpRequest.DONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data;
|
||||||
|
if (typeof xhr.response != 'object') {
|
||||||
|
try {
|
||||||
|
data = JSON.parse(xhr.response)
|
||||||
|
} catch(e) {
|
||||||
|
data = xhr.response;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data = xhr.response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xhr.status != opt.successCode) {
|
||||||
|
typeof opt.onfailed == 'function' && opt.onfailed(data, xhr);
|
||||||
|
} else if (typeof opt.onsucceed == 'function') {
|
||||||
|
opt.onsucceed(data, xhr);
|
||||||
|
}
|
||||||
|
|
||||||
|
typeof opt.onend == 'function' && opt.onend(xhr);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send(opt.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
class request {
|
||||||
|
constructor(url, method, data){
|
||||||
|
this._url = url;
|
||||||
|
this._method = method;
|
||||||
|
this._data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
do(){
|
||||||
|
sendXHR({
|
||||||
|
method: this._method,
|
||||||
|
url: this._url,
|
||||||
|
data: this._data,
|
||||||
|
successCode: this._successCode,
|
||||||
|
onsucceed: this._onsucceed,
|
||||||
|
onfailed: this._onfailed,
|
||||||
|
onexception: this._onexception,
|
||||||
|
onend: this._onend,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onsucceed(successCode, f){
|
||||||
|
this._successCode = successCode;
|
||||||
|
this._onsucceed = f;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
onfailed(f){
|
||||||
|
this._onfailed = f;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
onexception(f){
|
||||||
|
this._onexception = f;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
onend(f){
|
||||||
|
this._onend = f;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Rest {
|
||||||
|
constructor(prefix, defaultFailedHandler, defaultExceptionHandler){
|
||||||
|
this.prefix = prefix;
|
||||||
|
this.defaultFailedHandler = defaultFailedHandler; // function(url, resp){}
|
||||||
|
this.defaultExceptionHandler = defaultExceptionHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
GET(url){
|
||||||
|
return new request(this.prefix+url, 'GET')
|
||||||
|
.onfailed(this.defaultFailedHandler)
|
||||||
|
.onexception(this.defaultExceptionHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
POST(url, formdata){
|
||||||
|
return new request(this.prefix+url, 'POST', formdata)
|
||||||
|
.onfailed(this.defaultFailedHandler)
|
||||||
|
.onexception(this.defaultExceptionHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
PUT(url, formdata){
|
||||||
|
return new request(this.prefix+url, 'PUT', formdata)
|
||||||
|
.onfailed(this.defaultFailedHandler)
|
||||||
|
.onexception(this.defaultExceptionHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
DELETE(url){
|
||||||
|
return new request(this.prefix+url, 'DELETE')
|
||||||
|
.onfailed(this.defaultFailedHandler)
|
||||||
|
.onexception(this.defaultExceptionHandler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,18 @@ require('semantic-ui/dist/semantic.min.css');
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
Vue.config.debug = true;
|
Vue.config.debug = true;
|
||||||
|
|
||||||
|
// global restful client
|
||||||
|
import Rest from './libraries/rest-client.js';
|
||||||
|
const RestApi =(Vue, options)=>{
|
||||||
|
Vue.prototype.$rest = new Rest('/v1/');
|
||||||
|
};
|
||||||
|
Vue.use(RestApi);
|
||||||
|
|
||||||
|
// global event bus
|
||||||
|
Vue.use((Vue)=>{
|
||||||
|
Vue.prototype.$bus = new Vue();
|
||||||
|
});
|
||||||
|
|
||||||
import VueRouter from 'vue-router';
|
import VueRouter from 'vue-router';
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,12 @@ module.exports = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
|
proxy: {
|
||||||
|
'/v1': {
|
||||||
|
target: 'http://127.0.0.1:7079',
|
||||||
|
secure: false
|
||||||
|
}
|
||||||
|
},
|
||||||
historyApiFallback: true,
|
historyApiFallback: true,
|
||||||
noInfo: true
|
noInfo: true
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue