pull/19/head
Doflatango 2017-05-26 17:27:00 +08:00 committed by miraclesu
parent 67fa7cf643
commit 12a79c63dd
27 changed files with 3173 additions and 858 deletions

View File

@ -23,7 +23,7 @@ cp -r web/ui/dist "$out/ui"
sources=`find ./conf/files -name "*.json.sample"`
check_code
for source in $sources;do
yes | echo $source|sed "s/.*\/\(\w*\.json\).*/cp -f & .\/$out\/conf\/\1/"|bash
yes | echo $source|sed "s/.*\/\(.*\.json\).*/cp -f & .\/$out\/conf\/\1/"|bash
check_code
done

44
web/ui/dist/build.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
web/ui/dist/icons.eot vendored

Binary file not shown.

3346
web/ui/dist/icons.svg vendored

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 382 KiB

After

Width:  |  Height:  |  Size: 434 KiB

BIN
web/ui/dist/icons.ttf vendored

Binary file not shown.

BIN
web/ui/dist/icons.woff vendored

Binary file not shown.

Binary file not shown.

View File

@ -12,6 +12,7 @@
"dependencies": {
"chart.js": "^2.5.0",
"jquery": "^3.1.1",
"jquery.cookie": "^1.4.1",
"semantic-ui": "^2.2.7",
"vue": "^2.1.0",
"vue-router": "^2.2.1"

View File

@ -2,10 +2,19 @@
<div id="app">
<div class="ui blue inverted menu fixed">
<div class="item">CRONSUN</div>
<router-link class="item" to="/" v-bind:class="{active: this.$route.path == '/'}"><i class="dashboard icon"></i> 仪表盘</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="/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="server icon"></i> 节点</router-link>
<router-link class="item" to="/" v-bind:class="{active: this.$route.path == '/'}"><i class="dashboard icon"></i> {{$L('dashboard')}}</router-link>
<router-link class="item" to="/log" v-bind:class="{active: this.$route.path.indexOf('/log') === 0}"><i class="file text icon"></i> {{$L('log')}}</router-link>
<router-link class="item" to="/job" v-bind:class="{active: this.$route.path.indexOf('/job') === 0}"><i class="calendar icon"></i> {{$L('job')}}</router-link>
<router-link class="item" to="/node" v-bind:class="{active: this.$route.path.indexOf('/node') === 0}"><i class="server icon"></i> {{$L('node')}}</router-link>
<div ref="langSelection" class="ui right icon dropdown item">
<i class="world icon" style="margin-left:-1px; margin-right: 8px;"></i>
<span class="text">Language</span>
<i class="dropdown icon"></i>
<div class="menu">
<div class="item" v-for="lang in $Lang.supported" :data-value="lang.code">{{lang.name}}</div>
</div>
</div>
</div>
<div style="height: 55px;"></div>
<div class="ui container">
@ -20,8 +29,22 @@ import Messager from './components/Messager.vue';
export default {
name: 'app',
mounted: function(){
var vm = this;
$(this.$refs.langSelection).dropdown({
onChange: function(value, text){
var old = window.$.cookie('locale');
if (old !== value) {
window.$.cookie('locale', value)
window.location.reload()
}
}
});
},
components: {
Messager
}
},
}
</script>

View File

@ -22,7 +22,7 @@
<div class="ui fluid card">
<div class="content">
<div class="header number">{{totalJobs}}</div>
<div class="header title">任务总数</div>
<div class="header title">{{$L('total number of jobs')}}</div>
</div>
</div>
</div>
@ -30,7 +30,7 @@
<div class="ui fluid card">
<div class="content">
<div class="header number">{{totalExecuted}}</div>
<div class="header title">执行任务总次数</div>
<div class="header title">{{$L('total number of executeds')}}</div>
</div>
</div>
</div>
@ -38,7 +38,7 @@
<div class="ui fluid card">
<div class="content">
<div class="header number">{{todayExecuted}}</div>
<div class="header title">今日执行任务次数</div>
<div class="header title">{{$L('total number of executeds(today)')}}</div>
</div>
</div>
</div>
@ -48,7 +48,7 @@
<div class="ui fluid card">
<div class="content">
<div class="header number">{{totalNodes}}</div>
<div class="header title">节点总数</div>
<div class="header title">{{$L('total number of nodes')}}</div>
</div>
</div>
</div>
@ -58,7 +58,7 @@
<div id="charts">
<div class="ui card">
<div class="content">
<h4 class="header"><router-link to="node">当前节点状态</router-link></h4>
<h4 class="header"><router-link to="node">{{$L('node stat')}}</router-link></h4>
<div class="description">
<canvas ref="node"></canvas>
</div>
@ -67,7 +67,7 @@
<div class="ui card">
<div class="content">
<h4 class="header"><router-link :to="'log?begin='+today+'&end='+today">今日任务概况</router-link></h4>
<h4 class="header"><router-link :to="'log?begin='+today+'&end='+today">{{$L('executed stat(today)')}}</router-link></h4>
<div class="description">
<canvas ref="job"></canvas>
</div>
@ -108,7 +108,7 @@ export default {
new Chart($(vm.$refs.job), {
type: 'pie',
data: {
labels: ["成功:"+dailySuccessed, "失败:"+(dailytotal-dailySuccessed)],
labels: [vm.$L("{n} successed", dailySuccessed), vm.$L("{n} failed", dailytotal-dailySuccessed)],
datasets: [{
data: [dailySuccessed, dailytotal - dailySuccessed],
backgroundColor: ["#21BA45", "#DB2828"],
@ -136,7 +136,7 @@ export default {
new Chart($(vm.$refs.node), {
type: 'pie',
data: {
labels: ["在线:"+online, "离线:"+offline, "故障:"+damaged],
labels: [vm.$L("{n} online", online), vm.$L("{n} offline", offline), vm.$L("{n} damaged", damaged)],
datasets: [{
data: [online, offline, damaged],
backgroundColor: ["#21BA45", "#333", "#DB2828"],

View File

@ -1,13 +1,13 @@
<template>
<div class="ui modal">
<i class="close icon"></i>
<div class="header">执行任务 {{jobName}}</div>
<div class="header">{{$L('executing job: {job}', jobName)}}</div>
<div class="content">
<Dropdown title="选择执行的节点" :items="nodes" v-on:change="changeNode"></Dropdown>
<Dropdown :title="$L('node')" :items="nodes" v-on:change="changeNode"></Dropdown>
</div>
<div class="actions">
<div class="ui deny button">取消</div>
<div class="ui positive right labeled icon button">立刻执行任务 <i class="checkmark icon"></i></div>
<div class="ui deny button">{{$L('cancel')}}</div>
<div class="ui positive right labeled icon button">{{$L('execute now')}} <i class="checkmark icon"></i></div>
</div>
</div>
</template>

View File

@ -4,32 +4,32 @@
<template>
<div>
<div class="clearfix" style="margin-bottom: 20px;">
<router-link class="ui left floated button" to="/job/executing">查看执行中的任务</router-link>
<router-link class="ui left floated button" to="/job/executing">{{$L('view executing jobs')}}</router-link>
<button class="ui left floated icon button" v-on:click="refresh"><i class="refresh icon"></i></button>
<router-link class="ui right floated primary button" to="/job/create"><i class="add to calendar icon"></i> 新任务</router-link>
<router-link class="ui right floated primary button" to="/job/create"><i class="add to calendar icon"></i> {{$L('create job')}}</router-link>
</div>
<form class="ui form">
<div class="two fields">
<div class="field">
<label>任务分组</label>
<Dropdown title="选择分组" v-bind:items="groups" v-on:change="changeGroup" :selected="group"/>
<label>{{$L('group filter')}}</label>
<Dropdown :title="$L('select a group')" v-bind:items="groups" v-on:change="changeGroup" :selected="group"/>
</div>
<div class="field">
<label>节点过滤</label>
<Dropdown title="选择节点" v-bind:items="nodes" v-on:change="changeNode" :selected="node"/>
<label>{{$L('node filter')}}</label>
<Dropdown :title="$L('select a node')" v-bind:items="nodes" v-on:change="changeNode" :selected="node"/>
</div>
</div>
</form>
<table class="ui hover blue table" v-if="jobs.length > 0">
<thead>
<tr>
<th class="collapsing center aligned">操作</th>
<th class="collapsing center aligned">状态</th>
<th width="200px" class="center aligned">分组</th>
<th class="center aligned">用户</th>
<th class="center aligned">名称</th>
<th class="center aligned">最近执行时间</th>
<th class="center aligned">执行结果</th>
<th class="collapsing center aligned">{{$L('operation')}}</th>
<th class="collapsing center aligned">{{$L('status')}}</th>
<th width="200px" class="center aligned">{{$L('group')}}</th>
<th class="center aligned">{{$L('user')}}</th>
<th class="center aligned">{{$L('name')}}</th>
<th class="center aligned">{{$L('latest executed')}}</th>
<th class="center aligned">{{$L('executing result')}}</th>
</tr>
</thead>
<tbody>
@ -38,11 +38,11 @@
<div class="ui icon dropdown">
<i class="content icon"></i>
<div class="menu">
<div class="item" v-on:click="$router.push('/job/edit/'+job.group+'/'+job.id)">编辑</div>
<div class="item" v-if="job.pause" v-on:click="changeStatus(job.group, job.id, index, !job.pause)"></div>
<div class="item" v-if="!job.pause" v-on:click="changeStatus(job.group, job.id, index, !job.pause)"></div>
<div class="item" v-on:click="$router.push('/job/edit/'+job.group+'/'+job.id)">{{$L('edit')}}</div>
<div class="item" v-if="job.pause" v-on:click="changeStatus(job.group, job.id, index, !job.pause)">{{$L('open')}}</div>
<div class="item" v-if="!job.pause" v-on:click="changeStatus(job.group, job.id, index, !job.pause)">{{$L('pause')}}</div>
<div class="divider"></div>
<div class="item" style="color:red;" v-on:click="removeJob(job.group, job.id, index)">删除</div>
<div class="item" style="color:red;" v-on:click="removeJob(job.group, job.id, index)">{{$L('delete')}}</div>
</div>
</div>
</td>
@ -56,14 +56,14 @@
</td>
<td :class="{error: job.latestStatus && !job.latestStatus.success}">
<span v-if="!job.latestStatus">-</span>
<router-link v-else :to="'/log/'+job.latestStatus.refLogId">{{job.latestStatus.success ? '成功' : '失败'}}</router-link> |
<router-link v-else :to="'/log/'+job.latestStatus.refLogId">{{$L(job.latestStatus.success ? 'successed' : 'failed')}}</router-link> |
<router-link :to="{path: 'log', query: {latest:true, ids: job.id}}">latest</router-link> |
<a href="#" title="点此选择节点重新执行任务" v-on:click.prevent="showExecuteJobModal(job.name, job.group, job.id)"><i class="icon repeat"></i></a>
<a href="#" :title="$L('click to select a node and re-execute job')" v-on:click.prevent="showExecuteJobModal(job.name, job.group, job.id)"><i class="icon repeat"></i></a>
</td>
</tr>
</tbody>
</table>
<ExecuteJob ref="executeJobModal">
<ExecuteJob ref="executeJobModal"/>
</div>
</template>
@ -90,13 +90,13 @@ export default {
this.$rest.GET('job/groups').onsucceed(200, (resp)=>{
!resp.includes('default') && resp.unshift('default');
resp.unshift({value: '', name: '所有分组'});
resp.unshift({value: '', name: vm.$L('all groups')});
vm.groups = resp;
this.fetchList(this.buildQuery());
}).do();
this.$rest.GET('nodes').onsucceed(200, (resp)=>{
vm.nodes.push({name: '所有节点', value: ''});
vm.nodes.push({name: vm.$L('all nodes'), value: ''});
for (var i in resp) {
vm.nodes.push(resp[i].id);
}
@ -169,7 +169,7 @@ export default {
},
formatLatest: function(latest){
return formatTime(latest.beginTime, latest.endTime)+',于 '+latest.node+' 耗时 '+formatDuration(latest.beginTime, latest.endTime);
return this.$L('{begin ~ end}, on {node} took {times}', formatTime(latest.beginTime, latest.endTime), latest.node, formatDuration(latest.beginTime, latest.endTime))
},
showExecuteJobModal: function(jobName, jobGroup, jobId){

View File

@ -3,98 +3,97 @@
<div class="header"><i class="attention icon"></i> {{error}}</div>
</div>
<form v-else class="ui form" v-bind:class="{loading:loading}" v-on:submit.prevent>
<h3 class="ui header">{{action == 'CREATE' ? '添加' : '更新'}}任务&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<h3 class="ui header">{{$L(action == 'CREATE' ? 'create job' : 'update job')}}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<em v-if="job.id">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ID# {{job.id}}</em>
<div class="ui toggle checkbox" ref="pause">
<input type="checkbox" class="hidden" v-bind:checked="!job.pause">
<label v-bind:style="{color: (job.pause?'red':'green')+' !important'}">{{job.pause ? '任务暂停' : '任务开启'}}</label>
<label v-bind:style="{color: (job.pause?'red':'green')+' !important'}">{{$L(job.pause ? 'pause' : 'open')}}</label>
</div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<div class="ui toggle checkbox" ref="fail_notify" v-if="$appConfig.alarm">
<input type="checkbox" class="hidden" v-bind:checked="job.fail_notify">
<label>{{job.fail_notify ? '开启报警' : '关闭报警'}}</label>
<label>{{$L(job.fail_notify ? 'warning on' : 'warning off')}}</label>
</div>
</h3>
<div class="inline fields" ref="kind">
<label>任务类型</label>
<label>{{$L('job type')}}</label>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" v-model="job.kind" name="kind" value="0" tabindex="0" class="hidden"/>
<label>普通任务</label>
<label>{{$L('common job')}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" v-model="job.kind" name="kind" value="1" tabindex="0" class="hidden"/>
<label title="同一时间只有一个任务进程在某个节点上面执行">单机单进程
<i class="help circle link icon" data-position="top right" data-html="" data-variation="wide"></i>
<label>{{$L('single node single process')}}
<i class="help circle link icon" data-position="top right" :data-html="$L('at the same time only one job process is executed on one of node')" data-variation="wide"></i>
</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" v-model="job.kind" name="kind" value="2" tabindex="0" class="hidden"/>
<label>一个任务执行间隔内允许执行一次</label>
<label>{{$L('group level common')}}
<i class="help circle link icon" data-position="top right" :data-html="$L('group level common help')" data-variation="wide"></i>
</label>
</div>
</div>
</div>
<div class="two fields">
<div class="field">
<label>任务名称</label>
<input type="text" ref="name" v-bind:value="job.name" v-on:input="updateValue($event.target.value)" placeholder="任务名称">
<label>{{$L('job name')}}</label>
<input type="text" ref="name" v-bind:value="job.name" v-on:input="updateValue($event.target.value)" :placeholder="$L('job name')">
</div>
<div class="field">
<label>任务分组</label>
<Dropdown title="选择分组" v-bind:allowAdditions="true" v-bind:items="groups" v-bind:selected="job.group" v-on:change="changeGroup"></Dropdown>
<label>{{$L('job group')}}</label>
<Dropdown :title="$L('select a group')" v-bind:allowAdditions="true" v-bind:items="groups" v-bind:selected="job.group" v-on:change="changeGroup"></Dropdown>
</div>
</div>
<div class="fields">
<div class="twelve wide field">
<label>任务脚本 {{allowSuffixsTip}}</label>
<input type="text" v-model="job.cmd" placeholder="任务脚本">
<label>{{$L('script path')}} {{allowSuffixsTip}}</label>
<input type="text" v-model="job.cmd" :placeholder="$L('script path')">
</div>
<div class="four wide field">
<label>用户({{$appConfig.security.open ? '必选' : '可选'}})</label>
<Dropdown v-if="$appConfig.security.open" title="指定执行用户" v-bind:items="$appConfig.security.users" v-bind:selected="job.user" v-on:change="changeUser"></Dropdown>
<input v-else type="text" v-model="job.user" placeholder="指定执行用户">
<label>{{$L($appConfig.security.open ? 'user(required)' : 'user(optional)')}}</label>
<Dropdown v-if="$appConfig.security.open" :title="$L('the user which to execute the command')" v-bind:items="$appConfig.security.users" v-bind:selected="job.user" v-on:change="changeUser"></Dropdown>
<input v-else type="text" v-model="job.user" :placeholder="$L('the user which to execute the command')">
</div>
</div>
<div class="field" v-if="$appConfig.alarm && job.fail_notify">
<label>指定报警额外接受人</label>
<Dropdown title="邮件地址" v-bind:items="alarmReceivers" v-bind:selected="job.to" v-on:change="changeAlarmReceiver" v-bind:multiple="true" v-bind:allowAdditions="true"/>
<label>{{$L('warning receiver')}}</label>
<Dropdown :title="$L('e-mail address')" v-bind:items="alarmReceivers" v-bind:selected="job.to" v-on:change="changeAlarmReceiver" v-bind:multiple="true" v-bind:allowAdditions="true"/>
</div>
<div class="two fields">
<div class="field">
<label>超时设置单位0 表示不限制</label>
<input type="number" ref="timeout" v-model:value="job.timeout" placeholder="任务执行超时时间">
</div>
<div class="field">
<label>并行数设置0 表示不限制</label>
<div class="ui icon input">
<input type="number" ref="parallels" v-model:value="job.parallels" placeholder="任务执行超时时间">
<i class="large help circle link icon" data-position="top right" data-html="<strong style='color:red'>单个节点</strong>上面同时可执行多少个任务针对某些任务执行时间很长但两次任务执行间隔较短时比较有用" data-variation="wide"></i>
<label>{{$L('timeout(in seconds, 0 for no limits)')}}</label>
<input type="number" ref="timeout" v-model:value="job.timeout">
</div>
<div class="field" v-show="job.kind === 0">
<label>{{$L('parallel number in one node(0 for no limits)')}}</label>
<input type="number" ref="parallels" v-model:value="job.parallels">
</div>
</div>
<div class="two fields">
<div class="field">
<label>失败重试次数0 表示不重试</label>
<input type="number" ref="retry" v-model:value="job.retry" placeholder="任务失败后重试的次数">
<label>{{$L('retries(number of retries when failed, 0 means no retry)')}}</label>
<input type="number" ref="retry" v-model:value="job.retry">
</div>
<div class="field">
<label>失败重试间隔0 表示立即执行</label>
<input type="number" ref="interval" v-model:value="job.interval" placeholder="任务失败后多长时间再次执行">
<label>{{$L('retry interval(in seconds)')}}</label>
<input type="number" ref="interval" v-model:value="job.interval">
</div>
</div>
<div class="field">
<span v-if="!job.rules || job.rules.length == 0"><i class="warning circle icon"></i></span>
<span v-if="!job.rules || job.rules.length == 0"><i class="warning circle icon"></i>{{$L('the job dose not have a timer currently, please click the button below to add a timer')}}</span>
</div>
<JobEditRule v-for="(rule, index) in job.rules" :key="rule.id" v-bind: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>
<button class="fluid ui button" v-on:click="addNewTimer" type="button"><i class="history icon"></i> {{$L('add timer')}}</button>
</div>
<div class="field">
<button class="fluid blue ui button" type="button" v-on:click="submit"><i class="upload icon"></i> 保存任务</button>
<button class="fluid blue ui button" type="button" v-on:click="submit"><i class="upload icon"></i> {{$L('save job')}}</button>
</div>
</div>
</form>
@ -116,7 +115,7 @@ export default {
allowSuffixsTip: '',
job: {
id: '',
kind: 2, // 0 == 1 ==
kind: 0, // 0 == 1 ==
name: '',
oldGroup: '',
group: '',
@ -190,7 +189,7 @@ export default {
var secCnf = this.$appConfig.security;
if (secCnf.open) {
if (secCnf.ext && secCnf.ext.length > 0) {
this.allowSuffixsTip = '(当前限制只允许添加此类后缀脚本:' + secCnf.ext.join(' ') + '';
this.allowSuffixsTip = this.$L('(only [{.suffixs}] files can be allowed)', secCnf.ext.join(' '));
}
}

View File

@ -1,24 +1,24 @@
<template>
<div style="margin-bottom:20px;">
<h4 class="ui horizontal divider header">定时器 - {{index}} <a href="#" v-on:click.prevent="remove">删除</a></h4>
<h4 class="ui horizontal divider header">{{$L('timer')}} - {{index}} <a href="#" v-on:click.prevent="remove">{{$L('delete')}}</a></h4>
<div class="two fields">
<div class="field">
<div class="ui icon input">
<input type="text" v-bind:value="rule.timer" v-on:input="change('timer', $event.target.value)" placeholder="定时 * 5 * * * *"/>
<i ref="ruletip" class="large help circle link icon" data-position="top right" data-content="<> <分钟> <小时> <> <月份> <星期>规则与 crontab 一样" data-variation="wide"></i>
<i ref="ruletip" class="large help circle link icon" data-position="top right" :data-content="$L('<sec> <min> <hr> <day> <month> <week>, rule is same with Cron')" data-variation="wide"></i>
</div>
</div>
<div class="field">
<Dropdown title="节点分组" v-bind:items="nodeGroups" v-bind:selected="rule.gids" multiple="true" v-on:change="changeNodeGroups($event)"></Dropdown>
<Dropdown :title="$L('node group')" v-bind:items="nodeGroups" v-bind:selected="rule.gids" multiple="true" v-on:change="changeNodeGroups($event)"></Dropdown>
</div>
</div>
<div class="field">
<label>同时在这些节点上面运行任务</label>
<Dropdown title="选择节点" v-bind:items="activityNodes" v-bind:selected="rule.nids" v-on:change="changeIncludeNodes($event)" multiple="true"></Dropdown>
<label>{{$L('and please running on those nodes')}}</label>
<Dropdown :title="$L('select nodes')" v-bind:items="activityNodes" v-bind:selected="rule.nids" v-on:change="changeIncludeNodes($event)" multiple="true"></Dropdown>
</div>
<div class="field">
<label>不在这些节点上面运行任务</label>
<Dropdown title="选择节点" v-bind:items="activityNodes" v-bind:selected="rule.exclude_nids" v-on:change="changeExcludeNodes($event)" multiple="true"></Dropdown>
<label>{{$L('do not running on those nodes')}}</label>
<Dropdown :title="$L('select nodes')" v-bind:items="activityNodes" v-bind:selected="rule.exclude_nids" v-on:change="changeExcludeNodes($event)" multiple="true"></Dropdown>
</div>
</div>
</template>

View File

@ -4,35 +4,35 @@
<template>
<div>
<div class="clearfix" style="margin-bottom: 20px;">
<router-link class="ui left floated button" to="/job">查看任务列表</router-link>
<router-link class="ui left floated button" to="/job">{{$L('view job list')}}</router-link>
<button class="ui left floated icon button" v-on:click="refresh"><i class="refresh icon"></i></button>
<router-link class="ui right floated primary button" to="/job/create"><i class="add to calendar icon"></i> 新任务</router-link>
<router-link class="ui right floated primary button" to="/job/create"><i class="add to calendar icon"></i> {{$L('create job')}}</router-link>
</div>
<form class="ui form" v-bind:class="{loading:loading}" v-on:submit.prevent>
<div class="field">
<label>任务 ID</label>
<input type="text" ref="ids" v-model:value="ids" placeholder="多个 ID 使用英文逗号分隔"/>
<label>{{$L('job ID')}}</label>
<input type="text" ref="ids" v-model:value="ids" :placeholder="$L('multiple IDs can separated by commas')"/>
</div>
<div class="field">
<label>选择分组</label>
<Dropdown title="选择分组" v-bind:items="prefetchs.groups" v-on:change="changeGroup" :selected="groups" :multiple="true"/>
<label>{{$L('select group')}}</label>
<Dropdown :title="$L('select group')" v-bind:items="prefetchs.groups" v-on:change="changeGroup" :selected="groups" :multiple="true"/>
</div>
<div class="field">
<label>选择节点</label>
<Dropdown title="选择节点" v-bind:items="prefetchs.nodes" v-on:change="changeNodes" :selected="nodes" :multiple="true"/>
<label>{{$L('select nodes')}}</label>
<Dropdown :title="$L('select nodes')" v-bind:items="prefetchs.nodes" v-on:change="changeNodes" :selected="nodes" :multiple="true"/>
</div>
<div class="field">
<button class="fluid ui button" type="button" v-on:click="submit">查询</button>
<button class="fluid ui button" type="button" v-on:click="submit">{{$L('submit query')}}</button>
</div>
</form>
<table class="ui hover blue table" v-if="executings.length > 0">
<thead>
<tr>
<th class="center aligned">任务ID</th>
<th width="200px" class="center aligned">分组</th>
<th class="center aligned">节点</th>
<th class="center aligned">进程ID</th>
<th class="center aligned">开始时间</th>
<th class="center aligned">{{$L('job ID')}}</th>
<th width="200px" class="center aligned">{{$L('job group')}}</th>
<th class="center aligned">{{$L('node')}}</th>
<th class="center aligned">{{$L('process ID')}}</th>
<th class="center aligned">{{$L('starting time')}}</th>
</tr>
</thead>
<tbody>

View File

@ -3,25 +3,25 @@
<form class="ui form" method="GET" v-bind:class="{loading:loading}" v-on:submit.prevent>
<div class="two fields">
<div class="field">
<label>任务名称</label>
<input type="text" v-model="names" placeholder="多个名称用英文逗号分隔">
<label>{{$L('job name')}}</label>
<input type="text" v-model="names" :placeholder="$L('multiple names can separated by commas')">
</div>
<div class="field">
<label>任务 ID</label>
<input type="text" v-model="ids" placeholder="多个 ID 用英文逗号分隔">
<label>{{$L('job ID')}}</label>
<input type="text" v-model="ids" :placeholder="$L('multiple IDs can separated by commas')">
</div>
</div>
<div class="field">
<label>运行节点</label>
<input type="text" v-model="nodes" placeholder="ip多个 ip 用英文逗号分隔">
<label>{{$L('node')}}</label>
<input type="text" v-model="nodes" :placeholder="$L('multiple IPs can separated by commas')">
</div>
<div class="two fields">
<div class="field">
<label>开始时间</label>
<label>{{$L('starting date')}}</label>
<input type="date" v-model="begin">
</div>
<div class="field">
<label>结束时间</label>
<label>{{$L('end date')}}</label>
<input type="date" v-model="end">
</div>
</div>
@ -29,28 +29,28 @@
<div class="filed">
<div ref="latest" class="ui checkbox">
<input type="checkbox" class="hidden" v-model="latest">
<label>只看每个任务在每个节点上最后一次运行的结果</label>
<label>{{$L('latest result of each job on each node')}}</label>
</div>
<div class="filed">
<div ref="failedOnly" class="ui checkbox">
<input type="checkbox" class="hidden" v-model="failedOnly">
<label>只看失败的任务</label>
<label>{{$L('failure only')}}</label>
</div>
</div>
</div>
</div>
<div class="field">
<button class="fluid ui button" type="button" v-on:click="submit">查询</button>
<button class="fluid ui button" type="button" v-on:click="submit">{{$L('submit query')}}</button>
</div>
</form>
<table class="ui selectable green table" v-if="list && list.length > 0">
<thead>
<tr>
<th class="center aligned">任务名称</th>
<th class="center aligned">运行节点</th>
<th class="center aligned">执行用户</th>
<th class="center aligned">执行时间</th>
<th class="center aligned">运行结果</th>
<th class="center aligned">{{$L('job name')}}</th>
<th class="center aligned">{{$L('executing node')}}</th>
<th class="center aligned">{{$L('executing user')}}</th>
<th class="center aligned">{{$L('executing time')}}</th>
<th class="center aligned">{{$L('executing result')}}</th>
</tr>
</thead>
<tbody>
@ -60,8 +60,8 @@
<td>{{log.user}}</td>
<td :class="{warning: durationAttention(log.beginTime, log.endTime)}"><i class="attention icon" v-if="durationAttention(log.beginTime, log.endTime)"></i> {{formatTime(log)}}</td>
<td :class="{error: !log.success}">
<router-link :to="'/log/'+log.id">{{log.success ? '成功' : '失败'}}</router-link> |
<a href="#" title="点此选择节点重新执行任务" v-on:click.prevent="showExecuteJobModal(log.name, log.jobGroup, log.jobId)"><i class="icon repeat"></i></a>
<router-link :to="'/log/'+log.id">{{$L(log.success ? 'successed' : 'failed')}}</router-link> |
<a href="#" :title="$L('click to select a node and re-execute job')" v-on:click.prevent="showExecuteJobModal(log.name, log.jobGroup, log.jobId)"><i class="icon repeat"></i></a>
</td>
</tr>
</tbody>
@ -165,7 +165,7 @@ export default {
},
formatTime: function(log){
return formatTime(log.beginTime, log.endTime)+',于 '+log.node+' 耗时 '+formatDuration(log.beginTime, log.endTime);
return this.$L('{begin ~ end}, took {times}', formatTime(log.beginTime, log.endTime), formatDuration(log.beginTime, log.endTime));
},
showExecuteJobModal: function(jobName, jobGroup, jobId){

View File

@ -8,25 +8,25 @@
background: #e8e8e8;
text-align: center;
}
.notice {
color: red;
}
</style>
<template>
<div>
<div class="clearfix">
<router-link class="ui right floated primary button" to="/node/group"><i class="cubes icon"></i> 管理分组</router-link>
<router-link class="ui right floated primary button" to="/node/group"><i class="cubes icon"></i> {{$L('group manager')}}</router-link>
<div class="ui label"
<div class="ui label" v-for="item in items" v-bind:title="item.title">
<i class="cube icon" v-bind:class="item.css"></i> {{item.nodes.length}} {{item.name}}
<div class="ui label" v-for="item in items" v-bind:title="$L(item.title)">
<i class="cube icon" v-bind:class="item.css"></i> {{item.nodes.length}} {{$L(item.name)}}
</div>
{{count}} 个节点
<div class="ui label" title="当前版本号"> {{version}} </div>
{{$L('(total {n} nodes)', count)}}
<div class="ui label" :title="$L('currently version')"> {{version}} </div>
</div>
<div class="ui relaxed list" v-for="item in items">
<h4 v-if="item.nodes.length > 0" class="ui horizontal divider header"><i class="cube icon" v-bind:class="item.css"></i> {{item.name}} {{item.nodes.length}}</h4>
<h4 v-if="item.nodes.length > 0" class="ui horizontal divider header"><i class="cube icon" v-bind:class="item.css"></i> {{$L(item.name)}} {{item.nodes.length}}</h4>
<div v-for="node in item.nodes" class="node" v-bind:title="node.title">
<router-link class="item" v-bind:class="[(node.version == version) ? '' : 'notice']" :to="'/job?node='+node.id">{{node.id}}</router-link>
<router-link class="item" :to="'/job?node='+node.id">
<i class="red icon fork" v-if="node.version !== version" :title="$L('version inconsistent, node: {version}', node.version)"></i>
{{node.id}}
</router-link>
</div>
</div>
</div>
@ -38,9 +38,9 @@ export default {
data: function(){
return {
items: [
{nodes:[],name:'故障节点',title:'因自身或网络等原因未检测到节点存活',css:'red'},
{nodes:[],name:'离线节点',title:'手动下线/维护中的',css:''},
{nodes:[],name:'正常节点',title:'正常运行的节点',css:'green'}
{nodes:[],name:'node damaged',title:'node can not be deceted due to itself or network etc.',css:'red'},
{nodes:[],name:'node offline',title:'node is in maintenance or is shutdown manually',css:''},
{nodes:[],name:'node normaly',title:'node is running',css:'green'}
],
count: 0,
version: ''

View File

@ -20,7 +20,7 @@
<template>
<div>
<div class="clearfix">
<router-link class="ui right floated primary button" to="/node/group/create"><i class="add icon"></i> 新分组</router-link>
<router-link class="ui right floated primary button" to="/node/group/create"><i class="add icon"></i> {{$L('create group')}}</router-link>
<button class="ui right floated icon button" v-on:click="refresh"><i class="refresh icon"></i></button>
</div>
<div v-if="error != ''" class="header"><i class="attention icon"></i> {{error}}</div>

View File

@ -3,19 +3,19 @@
<div class="header"><i class="attention icon"></i> {{error}}</div>
</div>
<form v-else class="ui form" v-bind:class="{loading:loading}" v-on:submit.prevent>
<h3 class="ui header">{{action == 'CREATE' ? '添加' : '更新'}}节点分组</h3>
<h3 class="ui header">{{$L((action == 'CREATE' ? 'create' : 'update')+' node group')}}</h3>
<div class="field">
<input type="text" ref="name" v-model:value="group.name" placeholder="分组名称">
<input type="text" ref="name" v-model:value="group.name" :placeholder="$L('group name')">
</div>
<div class="field">
<label>分组节点</label>
<Dropdown title="选择节点" multiple="true" v-bind:items="allNodes" v-bind:selected="group.nids" v-on:change="changeGroup"/>
<label>{{$L('include nodes')}}</label>
<Dropdown :title="$L('select nodes')" multiple="true" v-bind:items="allNodes" v-bind:selected="group.nids" v-on:change="changeGroup"/>
</div>
<div class="field">
<button class="fluid blue ui button" type="button" v-on:click="submit"><i class="upload icon"></i> 保存分组</button>
<button class="fluid blue ui button" type="button" v-on:click="submit"><i class="upload icon"></i> {{$L('save group')}}</button>
</div>
<div class="field">
<button class="fluid red ui button" type="button" v-on:click="remove"><i class="remove icon"></i> 删除分组</button>
<button class="fluid red ui button" type="button" v-on:click="remove"><i class="remove icon"></i> {{$L('delete group')}}</button>
</div>
</form>
</template>
@ -86,7 +86,7 @@ export default {
},
remove(){
if (!confirm('确定删除该分组 ' + this.group.name + '?')) return;
if (!confirm(this.$L('are you sure to delete the group {name}?', this.group.name))) return;
var vm = this;
this.$rest.DELETE('node/group/'+this.group.id)
.onsucceed(204, ()=>{vm.$router.push('/node/group')})

View File

@ -19,7 +19,9 @@ export default {
},
mounted: function() {
if (!this.title || this.title.length === 0) this.title = '选择分组';
if (!this.title || this.title.length === 0) {
this.title = this.$L(multiple ? 'select groups' : 'select a group');
}
var vm = this;
$(this.$el).dropdown({

View File

@ -3,11 +3,11 @@
<div class="ui icon buttons">
<router-link :to="pageURL(_startPage-1)" class="ui button" :class="{disabled: _startPage<=1}"><i class="angle left icon"></i></router-link>
<router-link :to="pageURL(_startPage + n - 1)" v-for="n in _pageBtnNum" class="ui button" :class="{blue: _startPage+n-1 == _current}">{{_startPage + n-1}}</router-link>
<a class="ui button disabled"> {{_current}}/{{total}} </a>
<a class="ui button disabled">{{_current}}/{{total}}</a>
<router-link :to="pageURL(_startPage+length)" class="ui button" :class="{disabled: _startPage+length>total}"><i class="angle right icon"></i></router-link>
</div>
<div class="ui action input">
<input type="text" ref="gopage" style="width: 70px;" placeholder="跳转">
<input type="text" ref="gopage" style="width: 70px;">
<button class="ui icon button" v-on:click="go">
<i class="arrow right icon"></i>
</button>

View File

@ -0,0 +1,80 @@
import zhCN from './languages/zh-CN';
import en from './languages/en';
import jQuery from 'jquery';
require('jquery.cookie')
var languages = {
// key is lowercase
'en': en,
'zh-cn': zhCN
}
var supported = [
{name: 'English', code: 'en'},
{name: '简体中文', code: 'zh-CN'}
]
var language;
var locale = jQuery.cookie('locale') || navigator.language || 'en';
setLocale(locale);
function setLocale(loc) {
var loc2 = loc.toLowerCase()
if (languages[loc2]) {
locale = loc
language = languages[loc2]
}
}
function getLocale() {
return locale
}
function L(k) {
var t = language[k];
if (typeof t === 'undefined') {
t = k;
}
var tr = '';
var inTag = false;
var num = '';
var stash = '';
for (var i = 0; i < t.length; i++) {
var cc = t[i].charCodeAt();
if (cc === 123) { // {
if (inTag) {
tr += stash;
stash = '';
}
inTag = true;
} else if (inTag && cc >= 48 && cc <= 57) {
num += t[i];
} else if (inTag && cc === 125) { // }
var index = parseInt(num);
if (arguments.length - 1 > index) {
tr += arguments[index + 1];
}
num = '';
stash = '';
inTag = false;
continue;
} else {
inTag = false;
tr += stash;
stash = '';
}
if (inTag) {
stash += t[i];
} else {
tr += t[i];
}
}
return tr;
}
const lang = {getLocale, setLocale, supported, L};
export default lang;

View File

@ -0,0 +1,114 @@
var language = {
'dashboard': 'Dashboard',
'log': 'Log',
'job': 'Job',
'node': 'Node',
'total number of jobs': 'Total jobs',
'total number of executeds': 'Total executeds',
'total number of executeds(today)': 'Total executeds(today)',
'total number of nodes': 'Total nodes',
'{n} online': '{0} online',
'{n} offline': '{0} offline',
'{n} damaged': '{0} damaged',
'{n} successed': '{0} successed',
'{n} failed': '{0} failed',
'node stat': 'Node Stat',
'executed stat(today)': 'Executed Stat(today)',
'job name': 'Job name',
'multiple names can separated by commas': 'Multiple names can separated by commas',
'job ID': 'Job ID',
'multiple IDs can separated by commas': 'Multiple IDs can separated by commas',
'multiple IPs can separated by commas': 'Multiple IPs can separated by commas',
'starting date': 'Starting date',
'end date': 'End date',
'failure only': 'Failure only',
'latest result of each job on each node': 'Latest result of each job on each node',
'submit query': 'Submit query',
'executing node': 'Executing node',
'executing user': 'Executing user',
'executing time': 'Executing time',
'executing result': 'Executing result',
'successed': 'Successed',
'failed': 'Failed',
'click to select a node and re-execute job': 'Click to select a node and re-execute job',
'{begin ~ end}, took {times}': '{0}, took {1}',
'executing job: {job}': 'executing job: "{0}"',
'cancel': 'Cancel',
'execute now': 'Execute now!',
'view executing jobs': 'View executing jobs',
'view job list': 'View job list',
'starting time': 'Starting time',
'process ID': 'Process ID',
'group filter': 'Group filter',
'node filter': 'Node filter',
'select a group': 'Select a group',
'select a node': 'Select a node',
'operation': 'Operation',
'status': 'Status',
'group': 'Group',
'user': 'User',
'name': 'Name',
'latest executed': 'Latest executed',
'edit': 'Edit',
'open': 'Open',
'pause': 'Pause',
'delete': 'Delete',
'all groups': 'All groups',
'all nodes': 'All nodes',
'{begin ~ end}, on {node} took {times}': '{0}, on {1} took {2}',
'create job': 'Create job',
'update job': 'Update job',
'job type': 'Job type',
'common job': 'Common',
'single node single process': 'Single Node Single Process',
'at the same time only one job process is executed on the cluster': 'At the same time only one job process is executed on the cluster',
'group level common': 'Group Level Common',
'group level common help': 'It is difficult to name it, a simple way to explain is that we merge all selected nodes as a large node, then the kind of job behavior will looks like a Common job in a single node.',
'warning on': 'Warning ON',
'warning off': 'Warning OFF',
'job group': 'Job group',
'script path': 'Script path',
'(only [{.suffixs}] files can be allowed)': '(only [{0}] files can be allowed)',
'user(optional)': 'User(optional)',
'user(required)': 'User(required)',
'the user which to execute the command': 'which to exec the cmd',
'node group': 'Node group',
'warning receiver': 'Warning receiver',
'e-mail address': 'E-mail address',
'retries(number of retries when failed, 0 means no retry)': 'Retries(number of retries when failed, 0 means no retry)',
'retry interval(in seconds)': 'Retry interval(in seconds)',
'parallel number in one node(0 for no limits)': 'Parallel number in one node(0 for no limits)',
'timeout(in seconds, 0 for no limits)':'Timeout(in seconds, 0 for no limits)',
'<sec> <min> <hr> <day> <month> <week>, rule is same with Cron': '<sec> <min> <hr> <day> <month> <week>, rule is same with Cron',
'and please running on those nodes': 'And please running on those nodes',
'do not running on those nodes': 'Do not running on those nodes',
'the job dose not have a timer currently, please click the button below to add a timer': 'The job dose not have a timer currently, please click the button below to add a timer',
'timer': 'Timer',
'add timer': 'Add timer',
'save job': 'Save',
'node can not be deceted due to itself or network etc.': 'Node can not be deceted due to itself or network etc.',
'node is in maintenance or is shutdown manually': 'Node is in maintenance or is shutdown manually',
'node is running': 'Node is running, maybe',
'(total {n} nodes)': '(Total {0} nodes)',
'node damaged': 'Damaged',
'node offline': 'Offline',
'node normaly': 'Normal',
'currently version': 'Currently version',
'version inconsistent, node: {version}': 'Version inconsistent, node: {0}',
'group manager': 'Node manager',
'create node group': 'Create Node Group',
'update node group': 'Update Node Group',
'create group': 'Create group',
'group name': 'Group name',
'save group': 'Save',
'delete group': 'Delete',
'include nodes': 'Include nodes',
'select nodes': 'Select nodes',
'select groups': 'Select groups',
'are you sure to delete the group {name}?': 'Are you sure to delete the group {0}?'
}
export default language;

View File

@ -0,0 +1,116 @@
var language = {
'dashboard': '仪表盘',
'log': '日志',
'job': '任务',
'node': '节点',
'total number of jobs': '任务总数',
'total number of executeds': '执行任务总次数',
'total number of executeds(today)': '执行任务总次数(今天)',
'total number of nodes': '节点总数',
'{n} online': '在线 {0}',
'{n} offline': '离线 {0}',
'{n} damaged': '故障 {0}',
'{n} successed': '成功 {0}',
'{n} failed': '失败 {0}',
'node stat': '节点概况',
'executed stat(today)': '执行概况(今天)',
'job name': '任务名称',
'multiple names can separated by commas': '多个名称用英文逗号分隔',
'job ID': '任务 ID',
'multiple IDs can separated by commas': '多个 ID 用英文逗号分隔',
'multiple IPs can separated by commas': '多个 IP 用英文逗号分隔',
'starting date': '起始日期',
'end date': '截至日期',
'failure only': '只看失败的任务',
'latest result of each job on each node': '只看每个任务在每个节点上最后一次运行的结果',
'submit query': '查询',
'executing node': '执行节点',
'executing user': '执行用户',
'executing time': '执行时间',
'executing result': '执行结果',
'executing job': '执行任务',
'successed': '成功',
'failed': '失败',
'click to select a node and re-execute job': '点此选择节点重新执行任务',
'{begin ~ end}, took {times}': '{0},耗时 {1}',
'executing job: {job}': '执行任务:“{0}”',
'cancel': '取消',
'execute now': '立刻执行任务',
'view executing jobs': '查看执行中的任务',
'view job list': '查看任务列表',
'starting time': '开始时间',
'process ID': '进程ID',
'group filter': '分组过滤',
'node filter': '节点过滤',
'select a group': '选择分组',
'select a node': '选择节点',
'operation': '操作',
'status': '状态',
'group': '分组',
'user': '用户',
'name': '名称',
'latest executed': '最近执行时间',
'edit': '编辑',
'open': '开启',
'pause': '暂停',
'delete': '删除',
'all groups': '所有分组',
'all nodes': '所有节点',
'{begin ~ end}, on {node} took {times}': '{0}, 于 {1} 耗时 {2}',
'create job': '新建任务',
'update job': '更新任务',
'job type': '任务类型',
'common job': '普通任务',
'single node single process': '单机单进程',
'at the same time only one job process is executed on the cluster': '同一时间集群中只有一个任务进程在执行',
'group level common': '组级别普通任务',
'group level common help': '暂时没想到好名字,一个比较简单的说明是,把所有选中的节点视为一个大节点,那么该类型的任务就相当于在单个节点上的普通任务',
'warning on': '开启报警',
'warning off': '关闭报警',
'job group': '任务分组',
'script path': '任务脚本',
'(only [{.suffixs}] files can be allowed)': '(只允许 [{0}] 文件)',
'user(optional)': '用户(可选)',
'user(required)': '用户(必选)',
'the user which to execute the command': '指定执行的用户',
'node group': '节点分组',
'warning receiver': '报警接收人(任务失败时下面的地址也会接收到告警)',
'e-mail address': '邮件地址',
'retries(number of retries when failed, 0 means no retry)': '重试失败时重试次数0 为不重试)',
'retry interval(in seconds)': '失败重试间隔时间(秒)',
'parallel number in one node(0 for no limits)': '一个节点上面该任务并行数0 表示不限制)',
'timeout(in seconds, 0 for no limits)':'超时设置单位“秒”0 表示不限制)',
'<sec> <min> <hr> <day> <month> <week>, rule is same with Cron': '<秒> <分> <时> <日> <月> <周>,规则与 Cron 一样',
'and please running on those nodes': '同时在这些节点上面运行',
'do not running on those nodes': '不要在这些节点上面运行',
'the job dose not have a timer currently, please click the button below to add a timer': '当前任务没有定时器,点击下面按钮来添加定时器',
'timer': '定时器',
'add timer': '添加定时器',
'save job': '保存任务',
'node can not be deceted due to itself or network etc.': '因自身或网络等原因未检测到节点存活',
'node is in maintenance or is shutdown manually': '手动下线/维护中的',
'node is running': '正常运行的节点',
'(total {n} nodes)': '(共 {0} 节点)',
'node damaged': '故障节点',
'node offline': '离线节点',
'node normaly': '正常节点',
'currently version': '当前版本号',
'version inconsistent, node: {version}': '版本不一致,节点版本 {0}',
'group manager': '分组管理',
'create node group': '添加节点分组',
'update node group': '更新节点分组',
'create group': '新建分组',
'group name': '分组名称',
'save group': '保存分组',
'delete group': '删除分组',
'include nodes': '包含的节点',
'select nodes': '选择节点',
'select groups': '选择分组',
'are you sure to delete the group {name}?': '确定删除分组 {0}?'
}
export default language;

View File

@ -2,24 +2,24 @@ var formatDuration = function(beginTime, endTime){
var d = new Date(endTime) - new Date(beginTime);
var s = '';
var day = Math.floor(d/86400000);
if (day >= 1) s += day.toString() + ' ';
if (day >= 1) s += day.toString() + ' d ';
d = d%86400000;
var hour = Math.floor(d/3600000);
if (hour >= 1) s += hour.toString() + ' 小时 ';
if (hour >= 1) s += hour.toString() + ' hr ';
d = d%3600000;
var min = Math.floor(d/60000);
if (min >= 1) s += min.toString() + ' 分钟 ';
if (min >= 1) s += min.toString() + ' min ';
d = d%60000;
var sec = Math.floor(d/1000);
if (sec >= 1) s += sec.toString() + ' ';
if (sec >= 1) s += sec.toString() + ' s ';
d = Math.floor(d%1000);
if (d >= 1) s += d.toString() + ' 毫秒';
if (d >= 1) s += d.toString() + ' ms';
if (s.length == 0) s = "0 毫秒";
if (s.length == 0) s = '0 ms';
return s;
}

View File

@ -5,6 +5,12 @@ require('semantic-ui/dist/semantic.min.css');
import Vue from 'vue';
Vue.config.debug = true;
import Lang from './i18n/language';
Vue.use((Vue)=>{
Vue.prototype.$L = Lang.L
Vue.prototype.$Lang = Lang
});
// global restful client
import Rest from './libraries/rest-client.js';
var restApi = new Rest('/v1/');