├── common ├── __init__.py ├── json_util.py ├── string_util.py ├── datatable_result.py ├── xa_result.py └── page.py ├── models ├── __init__.py ├── job_increment_param_record.py ├── job_instance.py └── job_define.py ├── .gitignore ├── static └── assets │ ├── images │ ├── avatars │ │ ├── user.jpg │ │ ├── avatar.png │ │ ├── avatar1.png │ │ ├── avatar2.png │ │ ├── avatar3.png │ │ ├── avatar4.png │ │ ├── avatar5.png │ │ └── profile-pic.jpg │ ├── email │ │ ├── email1.png │ │ ├── email2.png │ │ ├── email3.png │ │ └── email4.png │ ├── gallery │ │ ├── image-1.jpg │ │ ├── image-2.jpg │ │ ├── image-3.jpg │ │ ├── image-4.jpg │ │ ├── image-5.jpg │ │ ├── image-6.jpg │ │ ├── thumb-1.jpg │ │ ├── thumb-2.jpg │ │ ├── thumb-3.jpg │ │ ├── thumb-4.jpg │ │ ├── thumb-5.jpg │ │ └── thumb-6.jpg │ ├── placeholder │ │ ├── 165x90.png │ │ ├── 255x150.png │ │ ├── 530x270.png │ │ └── 550x280.png │ ├── bootstrap-colorpicker │ │ ├── alpha.png │ │ ├── hue.png │ │ ├── saturation.png │ │ ├── hue-horizontal.png │ │ └── alpha-horizontal.png │ ├── gritter.png.html │ ├── ie-spacer.gif.html │ └── gritter-light.png.html │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular-.eot │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 │ ├── font-awesome │ └── 4.5.0 │ │ └── fonts │ │ ├── fontawesome-webfont-.eot │ │ ├── fontawesome-webfont-v=4.5.0.eot │ │ ├── fontawesome-webfont-v=4.5.0.ttf │ │ ├── fontawesome-webfont-v=4.5.0.woff │ │ └── fontawesome-webfont-v=4.5.0.woff2 │ ├── img │ ├── clear.png.html │ └── loading.gif.html │ ├── css │ ├── chosen-sprite.png.html │ ├── images │ │ ├── border.png.html │ │ ├── loading.gif.html │ │ ├── overlay.png.html │ │ ├── pattern.jpg.html │ │ ├── controls.png.html │ │ ├── meteorshower2.jpg.html │ │ └── loading_background.png.html │ ├── chosen-sprite@2x.png.html │ ├── prettify.min.css │ ├── fonts.googleapis.com.css │ ├── bootstrap-multiselect.min.css │ ├── bootstrap-duallistbox.min.css │ ├── jquery.jsonview.css │ ├── jquery.gritter.min.css │ ├── jquery-ui.custom.min.css │ ├── bootstrap-timepicker.min.css │ ├── colorbox.min.css │ ├── bootstrap-colorpicker.min.css │ ├── daterangepicker.min.css │ ├── bootstrap-datetimepicker.min.css │ └── dropzone.min.css │ └── js │ ├── jquery.ui.touch-punch.min.js │ ├── buttons.print.min.js │ ├── jquery.hotkeys.index.min.js │ ├── jquery.flot.resize.min.js │ ├── buttons.colVis.min.js │ ├── autosize.min.js │ ├── bootstrap-wysiwyg.min.js │ ├── jquery.dataTables.bootstrap.min.js │ ├── grid.locale-en.js │ ├── jquery.easypiechart.min.js │ ├── jquery.maskedinput.min.js │ ├── jquery.gritter.min.js │ ├── jquery.inputlimiter.min.js │ ├── bootstrap-tag.min.js │ ├── spin.js │ ├── ace-extra.min.js │ ├── util.js │ ├── spinbox.min.js │ ├── wizard.min.js │ ├── ace-editable.min.js │ ├── jquery.nestable.min.js │ ├── main.js │ └── tree.min.js ├── controller ├── index_controller.py ├── test.py ├── __init__.py ├── job_increment_param_record_controller.py ├── job_instance_controller.py ├── job_task_load.py └── job_define_controller.py ├── templates ├── index.html ├── job_define_json_view.html ├── job_instance_output_view.html ├── job_instance_json_view.html ├── job_increment_param_record.html ├── job_define_edit_modal.html ├── layout.html └── job_instance.html ├── run.py ├── config.py ├── app └── __init__.py └── README.md /common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | 3 | # Log file 4 | *.log 5 | 6 | *.pyc 7 | 8 | .idea/ 9 | -------------------------------------------------------------------------------- /static/assets/images/avatars/user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/avatars/user.jpg -------------------------------------------------------------------------------- /static/assets/images/email/email1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/email/email1.png -------------------------------------------------------------------------------- /static/assets/images/email/email2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/email/email2.png -------------------------------------------------------------------------------- /static/assets/images/email/email3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/email/email3.png -------------------------------------------------------------------------------- /static/assets/images/email/email4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/email/email4.png -------------------------------------------------------------------------------- /static/assets/images/avatars/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/avatars/avatar.png -------------------------------------------------------------------------------- /static/assets/images/avatars/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/avatars/avatar1.png -------------------------------------------------------------------------------- /static/assets/images/avatars/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/avatars/avatar2.png -------------------------------------------------------------------------------- /static/assets/images/avatars/avatar3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/avatars/avatar3.png -------------------------------------------------------------------------------- /static/assets/images/avatars/avatar4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/avatars/avatar4.png -------------------------------------------------------------------------------- /static/assets/images/avatars/avatar5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/avatars/avatar5.png -------------------------------------------------------------------------------- /static/assets/images/gallery/image-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/gallery/image-1.jpg -------------------------------------------------------------------------------- /static/assets/images/gallery/image-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/gallery/image-2.jpg -------------------------------------------------------------------------------- /static/assets/images/gallery/image-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/gallery/image-3.jpg -------------------------------------------------------------------------------- /static/assets/images/gallery/image-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/gallery/image-4.jpg -------------------------------------------------------------------------------- /static/assets/images/gallery/image-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/gallery/image-5.jpg -------------------------------------------------------------------------------- /static/assets/images/gallery/image-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/gallery/image-6.jpg -------------------------------------------------------------------------------- /static/assets/images/gallery/thumb-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/gallery/thumb-1.jpg -------------------------------------------------------------------------------- /static/assets/images/gallery/thumb-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/gallery/thumb-2.jpg -------------------------------------------------------------------------------- /static/assets/images/gallery/thumb-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/gallery/thumb-3.jpg -------------------------------------------------------------------------------- /static/assets/images/gallery/thumb-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/gallery/thumb-4.jpg -------------------------------------------------------------------------------- /static/assets/images/gallery/thumb-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/gallery/thumb-5.jpg -------------------------------------------------------------------------------- /static/assets/images/gallery/thumb-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/gallery/thumb-6.jpg -------------------------------------------------------------------------------- /static/assets/images/avatars/profile-pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/avatars/profile-pic.jpg -------------------------------------------------------------------------------- /static/assets/images/placeholder/165x90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/placeholder/165x90.png -------------------------------------------------------------------------------- /static/assets/images/placeholder/255x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/placeholder/255x150.png -------------------------------------------------------------------------------- /static/assets/images/placeholder/530x270.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/placeholder/530x270.png -------------------------------------------------------------------------------- /static/assets/images/placeholder/550x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/placeholder/550x280.png -------------------------------------------------------------------------------- /static/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/assets/images/bootstrap-colorpicker/alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/bootstrap-colorpicker/alpha.png -------------------------------------------------------------------------------- /static/assets/images/bootstrap-colorpicker/hue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/bootstrap-colorpicker/hue.png -------------------------------------------------------------------------------- /static/assets/fonts/glyphicons-halflings-regular-.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/fonts/glyphicons-halflings-regular-.eot -------------------------------------------------------------------------------- /static/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /static/assets/images/bootstrap-colorpicker/saturation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/bootstrap-colorpicker/saturation.png -------------------------------------------------------------------------------- /static/assets/images/bootstrap-colorpicker/hue-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/bootstrap-colorpicker/hue-horizontal.png -------------------------------------------------------------------------------- /static/assets/font-awesome/4.5.0/fonts/fontawesome-webfont-.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/font-awesome/4.5.0/fonts/fontawesome-webfont-.eot -------------------------------------------------------------------------------- /static/assets/images/bootstrap-colorpicker/alpha-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/images/bootstrap-colorpicker/alpha-horizontal.png -------------------------------------------------------------------------------- /static/assets/font-awesome/4.5.0/fonts/fontawesome-webfont-v=4.5.0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/font-awesome/4.5.0/fonts/fontawesome-webfont-v=4.5.0.eot -------------------------------------------------------------------------------- /static/assets/font-awesome/4.5.0/fonts/fontawesome-webfont-v=4.5.0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/font-awesome/4.5.0/fonts/fontawesome-webfont-v=4.5.0.ttf -------------------------------------------------------------------------------- /static/assets/font-awesome/4.5.0/fonts/fontawesome-webfont-v=4.5.0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/font-awesome/4.5.0/fonts/fontawesome-webfont-v=4.5.0.woff -------------------------------------------------------------------------------- /static/assets/font-awesome/4.5.0/fonts/fontawesome-webfont-v=4.5.0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoce/bt-ware-datasync-datax/HEAD/static/assets/font-awesome/4.5.0/fonts/fontawesome-webfont-v=4.5.0.woff2 -------------------------------------------------------------------------------- /controller/index_controller.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | from app import app 4 | from flask import render_template 5 | 6 | 7 | @app.route('/', methods=['GET']) 8 | @app.route('/index', methods=['GET']) 9 | def index(): 10 | return render_template('index.html') 11 | 12 | -------------------------------------------------------------------------------- /common/json_util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | 6 | 7 | class JsonUtil(object): 8 | 9 | @staticmethod 10 | def is_json(jsonStr): 11 | try: 12 | json.loads(jsonStr) 13 | except ValueError: 14 | return False 15 | return True 16 | 17 | -------------------------------------------------------------------------------- /common/string_util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | class StringUtil(object): 5 | @staticmethod 6 | def isNotBlank(s): 7 | if s.strip() == '': 8 | return False 9 | return True 10 | 11 | @staticmethod 12 | def isBlank(s): 13 | return not StringUtil.isNotBlank(s) 14 | 15 | -------------------------------------------------------------------------------- /static/assets/img/clear.png.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/img/clear.png was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /static/assets/img/loading.gif.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/img/loading.gif was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /static/assets/images/gritter.png.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/images/gritter.png was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /static/assets/css/chosen-sprite.png.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/css/chosen-sprite.png was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /static/assets/css/images/border.png.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/css/images/border.png was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /static/assets/css/images/loading.gif.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/css/images/loading.gif was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /static/assets/css/images/overlay.png.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/css/images/overlay.png was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /static/assets/css/images/pattern.jpg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/css/images/pattern.jpg was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /static/assets/images/ie-spacer.gif.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/images/ie-spacer.gif was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /static/assets/css/chosen-sprite@2x.png.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/css/chosen-sprite@2x.png was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /static/assets/css/images/controls.png.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/css/images/controls.png was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /static/assets/images/gritter-light.png.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/images/gritter-light.png was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /static/assets/css/images/meteorshower2.jpg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/css/images/meteorshower2.jpg was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /static/assets/css/images/loading_background.png.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 |

Not Found

8 |

The requested URL /assets/css/images/loading_background.png was not found on this server.

9 | 10 | -------------------------------------------------------------------------------- /controller/test.py: -------------------------------------------------------------------------------- 1 | import commands 2 | import croniter 3 | 4 | 5 | # (status, output) = commands.getstatusoutput('/Users/huan/software/datax/bin/datax.py /Users/huan/software/datax/job/94853791-f6ce-11e8-8271-4a0001c796d0.json') 6 | # 7 | # print status 8 | # 9 | # print output 10 | from apscheduler.triggers.cron import CronTrigger 11 | 12 | cron = CronTrigger.from_crontab(' */5 * * * *') 13 | print cron 14 | print type(cron) -------------------------------------------------------------------------------- /common/datatable_result.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask import jsonify 5 | 6 | 7 | success_res = dict() 8 | error_res = dict() 9 | 10 | 11 | class DataTableResult(object): 12 | @staticmethod 13 | def format(data=None): 14 | success_res['aaData'] = data['items'] 15 | success_res['iTotalRecords'] = data['total'] 16 | success_res['iTotalDisplayRecords'] = data['total'] 17 | return jsonify(success_res) 18 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block title %}控制台{% endblock %} 4 | 5 | {% block main_content %} 6 | 7 |
8 |
9 | 14 | 15 |
16 |
17 | {% endblock %} -------------------------------------------------------------------------------- /static/assets/css/prettify.min.css: -------------------------------------------------------------------------------- 1 | .com{color:#93a1a1}.lit{color:#195f91}.clo,.opn,.pun{color:#93a1a1}.fun{color:#dc322f}.atv,.str{color:#D14}.kwd,.prettyprint .tag{color:#1e347b}.atn,.dec,.typ,.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} -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from apscheduler.triggers.cron import CronTrigger 4 | from mongoengine import Q 5 | 6 | from app import app, scheduler 7 | from controller import job_define_controller 8 | from controller import job_instance_controller 9 | from controller import index_controller 10 | from controller import job_increment_param_record_controller 11 | from controller import job_task_load 12 | from models.job_define import JobDefine 13 | 14 | if __name__ == '__main__': 15 | app.run(debug=False) 16 | 17 | 18 | -------------------------------------------------------------------------------- /models/job_increment_param_record.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import datetime 5 | 6 | from app import db 7 | 8 | 9 | class JobIncrementParamRecord(db.Document): 10 | jobName = db.StringField(max_length=255, required=True) 11 | fromInstance = db.StringField() # 来自哪个job实例 12 | incrementParam = db.StringField(max_length=255, required=True) 13 | incrementVal = db.StringField(max_length=255, required=True) # 增量参考值建议都采用最后修改时间,便于更新、逻辑删除的处理 14 | recordingTime = db.DateTimeField(default=datetime.datetime.now, required=True) 15 | -------------------------------------------------------------------------------- /static/assets/css/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("http://fonts.gstatic.com/s/opensans/v13/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("http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff") format('woff'); 12 | } 13 | -------------------------------------------------------------------------------- /common/xa_result.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask import jsonify 5 | 6 | success_res = dict() 7 | error_res = dict() 8 | 9 | 10 | class XaResult(object): 11 | @staticmethod 12 | def success(data=None): 13 | success_res['data'] = data 14 | success_res['level'] = 'success' 15 | success_res['title'] = 'Success' 16 | return jsonify(success_res) 17 | 18 | @staticmethod 19 | def error(msg=''): 20 | error_res['level'] = 'danger' 21 | error_res['title'] = 'Danger!' 22 | error_res['msg'] = msg 23 | return jsonify(error_res) 24 | -------------------------------------------------------------------------------- /models/job_instance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import datetime 5 | import time 6 | 7 | from app import db 8 | 9 | 10 | class JobInstance(db.Document): 11 | instanceId = db.StringField() 12 | jobName = db.StringField() 13 | jobDisplayName = db.StringField() 14 | jobJson = db.StringField() # 来自哪个job实例 15 | jobJsonPath = db.StringField() 16 | result = db.IntField() 17 | executeOutput = db.StringField() # 输出结果 18 | executeTime = db.DecimalField(default=time.time, required=True) # 执行时间 19 | endTime = db.DecimalField(default=time.time, required=True) #执行结束时间 20 | -------------------------------------------------------------------------------- /models/job_define.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import datetime 5 | 6 | from app import db 7 | 8 | 9 | class JobDefine(db.Document): 10 | name = db.StringField(max_length=255, required=True) 11 | displayName = db.StringField() 12 | defJson = db.StringField() 13 | isIncrement = db.BooleanField() 14 | incrementParam = db.StringField(max_length=255) 15 | incrementParamStart = db.StringField(max_length=255) 16 | jobCron = db.StringField() 17 | createTime = db.DateTimeField(default=datetime.datetime.now, required=True) 18 | isDelete = db.BooleanField(default=False) 19 | isRun = db.BooleanField(default=False) 20 | 21 | 22 | -------------------------------------------------------------------------------- /controller/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 启动时加载数据库中标记为 运行中 的job 5 | from apscheduler.triggers.cron import CronTrigger 6 | from mongoengine import Q 7 | 8 | from app import scheduler, app 9 | from controller import job_task_load 10 | from models.job_define import JobDefine 11 | 12 | jobDefines = JobDefine.objects(Q(isDelete=False) & Q(isRun=True)) 13 | if jobDefines: 14 | for jobDefine in jobDefines: 15 | cron = CronTrigger.from_crontab(jobDefine.jobCron) 16 | scheduler.add_job(id=jobDefine.name, func=job_task_load.to_job_instance, trigger=cron, args=(jobDefine.name,)) 17 | JobDefine.objects(name=jobDefine.name).update_one(isRun=True) 18 | app.logger.info(u'%s 任务已启动,已在服务启动时加载成功!', jobDefine.name) 19 | else: 20 | app.logger.info(u'在服务启动时,并没有发现需要加载的Job!') -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | 5 | import logging 6 | 7 | BASEDIR = os.path.abspath(os.path.dirname(__file__)) 8 | 9 | 10 | class Config(object): 11 | # MongoDB 配置 12 | MONGODB_SETTINGS = {'db': 'datasync_datax', 13 | 'host': '192.168.100.85', 14 | 'port': 27017 15 | } 16 | # 启动调度任务Api 17 | SCHEDULER_API_ENABLED = True 18 | 19 | # DataX 任务脚本存放目录 20 | DATAX_JOB_JSON_FILE_PATH = '/Users/huan/software/datax/job/' 21 | 22 | # DataX 运行文件目录 23 | DATAX_PY_PATH = '/Users/huan/software/datax/bin/datax.py' 24 | 25 | # 日志目录 26 | LOG_FILE_PATH = '/Users/huan/sources/python-sources/bt-ware-datasync-datax/bt-ware-datasync-datax.log' 27 | 28 | # 日志级别 29 | LOG_LEVEL = logging.DEBUG 30 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask import Flask 5 | import logging 6 | from flask_mongoengine import MongoEngine 7 | from flask_apscheduler import APScheduler 8 | from config import Config 9 | 10 | app = Flask(__name__, static_folder='../static/assets', template_folder='../templates') 11 | 12 | app.config.from_object(Config) 13 | 14 | handler = logging.FileHandler(app.config['LOG_FILE_PATH'], encoding='UTF-8') 15 | logging_format = logging.Formatter( 16 | '%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s') 17 | handler.setFormatter(logging_format) 18 | app.logger.addHandler(handler) 19 | 20 | app.logger.setLevel(app.config['LOG_LEVEL']) 21 | 22 | 23 | db = MongoEngine(app) 24 | 25 | scheduler = APScheduler() 26 | scheduler.init_app(app) 27 | scheduler.start() 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /templates/job_define_json_view.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/job_instance_output_view.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/job_instance_json_view.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/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.checkbox,.multiselect-container>li>a>label.radio{margin:0}.multiselect-container>li>a>label>input[type=checkbox]{margin-bottom:5px}.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} -------------------------------------------------------------------------------- /common/page.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | class Page(object): 6 | def __init__(self, has_next=False, has_prev=False, pages=0, next_num=0, prev_num=0, total=0, items=None): 7 | self.items = items 8 | self.total = total 9 | self.prev_num = prev_num 10 | self.pages = pages 11 | self.has_prev = has_prev 12 | self.has_next = has_next 13 | self.next_num = next_num 14 | self.has_next = has_next 15 | 16 | def json_serialize(self): 17 | return { 18 | 'has_next': self.has_next, 19 | 'has_prev': self.has_prev, 20 | 'pages': self.pages, 21 | 'next_num': self.next_num, 22 | 'prev_num': self.prev_num, 23 | 'total': self.total, 24 | 'items': self.items 25 | } 26 | 27 | @staticmethod 28 | def from_paginate(paginate): 29 | return Page(has_next=paginate.has_next, has_prev=paginate.has_prev, pages=paginate.pages, 30 | next_num=paginate.next_num, prev_num=paginate.prev_num, total=paginate.total, 31 | items=paginate.items) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 轻量级数据同步工具 2 | ========== 3 | ## 功能 4 | 1.数据同步,支持不同数据库平台(参考datax支持) 5 | 2.定时数据同步 6 | 3.定时增量数据同步 7 | 4.数据同步任务管理及执行日志查阅 8 | 9 | ## 依赖 10 | 1.JDK 1.8+ 11 | 2.Python 2.7 12 | 3.Flask (管理系统界面) 13 | 4.Mongodb(记录执行记录) 14 | 5.gunicorn(Flask启动服务器) 15 | 16 | ## 安装 17 | #### 准备环境 18 | 1.安装MongoDB 19 | 2.安装Python 2.7 20 | 3.安装pip 21 | 4.安装JDK1.8并配置环境变量 22 | 5.安装Datax 23 | Datax github:https://github.com/alibaba/DataX 24 | 6.安装依赖Flask、flask_mongoengine、flask_apscheduler、gunicorn 25 | (启动时如果还有依赖问题,请按依赖提示安装依赖模块) 26 | 27 | #### 配置文件 28 | cd <项目路径> 29 | vim config.py 30 | 31 | #### 启动 32 | 启动方式依赖gunicorn,请确保已经安装依赖模块。 33 | 启动命令建议:gunicorn -b 0.0.0.0:5001 -D run:app 34 | 不建议使用 -w 参数指定进程数,因为APScheduler初始化时会加载多次,顺便会加载数据库中标记为启动的任务,也会多次加载,多次运行。 35 | 虽可以通过文件锁等方式解决重复加载问题,但还是变成了单点,前端管理后台访问频率相对较低,所以直接启动就好。 36 | 后期将借助分布式锁,解决负载均衡下的任务调度问题,以避免单点故障。 37 | 38 | #### 访问 39 | http://127.0.0.1:5001 40 | 41 | #### 其他 42 | 可评论交流 43 | 44 | #### 计划 45 | 1.增加用户、权限管理 46 | 2.完善控制台、报表功能 47 | 3.负载均衡及任务锁机制# bt-ware-datasync-datax 48 | -------------------------------------------------------------------------------- /static/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){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}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"),b(a,"mousemove"),b(a,"mousedown"))},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); -------------------------------------------------------------------------------- /static/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 .moveall,.bootstrap-duallistbox-container .removeall{width:40%}.bootstrap-duallistbox-container.bs2compatible .btn-group>.btn+.btn{margin-left:0}.bootstrap-duallistbox-container select{border-top-left-radius:0;border-top-right-radius:0;width:100%;height:300px;padding:0}.bootstrap-duallistbox-container .filter{display:inline-block;width:100%;height:31px;margin:0 0 5px;-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%} -------------------------------------------------------------------------------- /static/assets/css/jquery.jsonview.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | .jsonview { 3 | /*font-family: monospace; 4 | font-size: 1.1em;*/ 5 | white-space: pre-wrap; } 6 | .jsonview .prop { 7 | font-weight: bold; } 8 | .jsonview .null { 9 | color: red; } 10 | .jsonview .bool { 11 | color: #fde3a7; } 12 | .jsonview .num { 13 | color: #bf55ec; } 14 | .jsonview .string { 15 | color: #00b16a; 16 | white-space: pre-wrap; } 17 | .jsonview .string.multiline { 18 | display: inline-block; 19 | vertical-align: text-top; } 20 | .jsonview .collapser { 21 | position: absolute; 22 | left: -1em; 23 | cursor: pointer; } 24 | .jsonview .collapsible { 25 | transition: height 1.2s; 26 | transition: width 1.2s; } 27 | .jsonview .collapsible.collapsed { 28 | height: .8em; 29 | width: 1em; 30 | display: inline-block; 31 | overflow: hidden; 32 | margin: 0; } 33 | .jsonview .collapsible.collapsed:before { 34 | content: "…"; 35 | width: 1em; 36 | margin-left: .2em; } 37 | .jsonview .collapser.collapsed { 38 | transform: rotate(0deg); } 39 | .jsonview .q { 40 | display: inline-block; 41 | width: 0px; 42 | color: transparent; } 43 | .jsonview li { 44 | position: relative; } 45 | .jsonview ul { 46 | list-style: none; 47 | margin: 0 0 0 2em; 48 | padding: 0; } 49 | .jsonview h1 { 50 | font-size: 1.2em; } 51 | 52 | /*# sourceMappingURL=jquery.jsonview.css.map */ 53 | -------------------------------------------------------------------------------- /static/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;background:url("../images/ie-spacer.gif.html")}.gritter-top{background:url("../images/gritter.png.html") left -30px no-repeat;height:10px}.hover .gritter-top{background-position:right -30px}.gritter-bottom{background:url("../images/gritter.png.html") left bottom no-repeat;height:8px;margin:0}.hover .gritter-bottom{background-position:bottom right}.gritter-item{display:block;background:url("../images/gritter.png.html") left -40px no-repeat;color:#eee;padding:2px 11px 8px;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.html") left top no-repeat;cursor:pointer;width:30px;height:30px;text-indent:-9999em}.gritter-title{font-size:14px;font-weight:700;padding:0 0 7px;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-bottom,.gritter-light .gritter-close,.gritter-light .gritter-item,.gritter-light .gritter-top{background-image:url("../images/gritter-light.png.html");color:#222}.gritter-light .gritter-title{text-shadow:none} -------------------------------------------------------------------------------- /static/assets/js/buttons.print.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Print button for Buttons and DataTables. 3 | * 2015 SpryMedia Ltd - datatables.net/license 4 | */ 5 | !function(a){"function"==typeof define&&define.amd?define(["jquery","datatables.net","datatables.net-buttons"],function(b){return a(b,window,document)}):"object"==typeof exports?module.exports=function(b,c){return b||(b=window),c&&c.fn.dataTable||(c=require("datatables.net")(b,c).$),c.fn.dataTable.Buttons||require("datatables.net-buttons")(b,c),a(c,b,b.document)}:a(jQuery,window,document)}(function(a,b,c,d){"use strict";var e=a.fn.dataTable,f=c.createElement("a"),g=function(b){var c,d=a(b).clone()[0];return"link"===d.nodeName.toLowerCase()&&(f.href=d.href,c=f.host,-1===c.indexOf("/")&&0!==f.pathname.indexOf("/")&&(c+="/"),d.href=f.protocol+"//"+c+f.pathname+f.search),d.outerHTML};return e.ext.buttons.print={className:"buttons-print",text:function(a){return a.i18n("buttons.print","Print")},action:function(c,d,e,f){var h=d.buttons.exportData(f.exportOptions),i=function(a,b){for(var c="",d=0,e=a.length;e>d;d++)c+="<"+b+">"+a[d]+"";return c+""},j='';f.header&&(j+=""+i(h.header,"th")+""),j+="";for(var k=0,l=h.body.length;l>k;k++)j+=i(h.body[k],"td");j+="",f.footer&&(j+=""+i(h.footer,"th")+"");var m=b.open("",""),n=f.title;"function"==typeof n&&(n=n()),-1!==n.indexOf("*")&&(n=n.replace("*",a("title").text())),m.document.close();var o=""+n+"";a("style, link").each(function(){o+=g(this)}),a(m.document.head).html(o),a(m.document.body).html("

"+n+"

"+f.message+"
"+j),f.customize&&f.customize(m),setTimeout(function(){f.autoPrint&&(m.print(),m.close())},250)},title:"*",message:"",exportOptions:{},header:!0,footer:!1,autoPrint:!0,customize:null},e.Buttons}); -------------------------------------------------------------------------------- /controller/job_increment_param_record_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import commands 4 | import time 5 | 6 | import datetime 7 | 8 | import operator 9 | from flask import render_template, jsonify 10 | from flask import request, session 11 | from mongoengine import Q 12 | 13 | from app import app 14 | from common.datatable_result import DataTableResult 15 | from common.xa_result import XaResult 16 | from common.string_util import StringUtil 17 | from common.json_util import JsonUtil 18 | from models.job_define import JobDefine 19 | from models.job_increment_param_record import JobIncrementParamRecord 20 | from models.job_instance import JobInstance 21 | from common.page import Page 22 | from app import scheduler 23 | import uuid 24 | from models.job_instance import JobInstance 25 | 26 | 27 | @app.route('/job_increment_param_record', methods=['GET']) 28 | def to_job_increment_param_record(): 29 | return render_template('job_increment_param_record.html') 30 | 31 | 32 | @app.route('/job_increment_param_record/list', methods=['POST']) 33 | def increment_param_record_list(): 34 | display_start = int(request.form['iDisplayStart']) if 'iDisplayStart' in request.form else 1 35 | per_page = int(request.form['iDisplayLength']) if 'iDisplayLength' in request.form else 10 36 | 37 | page = display_start / per_page 38 | 39 | search = request.form['sSearch'] if 'sSearch' in request.form else '' 40 | 41 | limit_time = (datetime.datetime.now() - datetime.timedelta(days=3)) 42 | 43 | q_list = [Q(recordingTime__gt=limit_time)] 44 | if StringUtil.isNotBlank(search): 45 | q_list.append(Q(jobName__contains=search)) 46 | 47 | jobIncrementParamRecords = JobIncrementParamRecord.objects(reduce(operator.and_, q_list)).order_by('-recordingTime').paginate(page=page + 1 if page > 0 else 1, per_page=per_page) 48 | return DataTableResult.format(Page.from_paginate(jobIncrementParamRecords).json_serialize()) 49 | -------------------------------------------------------------------------------- /static/assets/js/jquery.hotkeys.index.min.js: -------------------------------------------------------------------------------- 1 | /*jslint browser: true*/ 2 | /*jslint jquery: true*/ 3 | !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||!(a.hotkeys.options.filterInputAcceptingElements&&a.hotkeys.textInputTypes.test(b.target.nodeName)||a.hotkeys.options.filterContentEditable&&a(b.target).attr("contenteditable")||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.2.0",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"],textInputTypes:/textarea|input|select/i,options:{filterInputAcceptingElements:!0,filterTextInputs:!0,filterContentEditable:!0}},a.each(["keydown","keyup","keypress"],function(){a.event.special[this]={add:b}})}(jQuery||this.jQuery||window.jQuery); -------------------------------------------------------------------------------- /controller/job_instance_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import commands 4 | import time 5 | 6 | import datetime 7 | 8 | import operator 9 | from flask import render_template, jsonify 10 | from flask import request, session 11 | from mongoengine import Q 12 | 13 | from app import app 14 | from common.datatable_result import DataTableResult 15 | from common.xa_result import XaResult 16 | from common.string_util import StringUtil 17 | from common.json_util import JsonUtil 18 | from models.job_define import JobDefine 19 | from models.job_increment_param_record import JobIncrementParamRecord 20 | from models.job_instance import JobInstance 21 | from common.page import Page 22 | from app import scheduler 23 | import uuid 24 | from models.job_instance import JobInstance 25 | 26 | 27 | @app.route('/job_instance', methods=['GET']) 28 | def to_job_instance(): 29 | return render_template('job_instance.html') 30 | 31 | 32 | @app.route('/job_instance/list', methods=['POST']) 33 | def instanclist(): 34 | display_start = int(request.form['iDisplayStart']) if 'iDisplayStart' in request.form else 1 35 | per_page = int(request.form['iDisplayLength']) if 'iDisplayLength' in request.form else 10 36 | 37 | page = display_start / per_page 38 | 39 | search = request.form['sSearch'] if 'sSearch' in request.form else '' 40 | 41 | limit_time = time.mktime((datetime.datetime.now() - datetime.timedelta(days=3)).timetuple()) * 1000 42 | 43 | q_list = [Q(executeTime__gt=limit_time)] 44 | 45 | if StringUtil.isNotBlank(search): 46 | q_list.append(Q(name__contains=search)) 47 | 48 | jobInstances = JobInstance.objects(reduce(operator.and_, q_list)).order_by('-executeTime').paginate( 49 | page=page + 1 if page > 0 else 1, per_page=per_page) 50 | return DataTableResult.format(Page.from_paginate(jobInstances).json_serialize()) 51 | 52 | 53 | @app.route('/job_instance/get', methods=['POST']) 54 | def instance_getone(): 55 | instanceId = request.values['instanceId'] if 'instanceId' in request.values else '' 56 | jobInstance = JobInstance.objects((Q(instanceId=instanceId))).first() 57 | if jobInstance is None: 58 | return XaResult.error(msg=u'任务实例不存在') 59 | return XaResult.success(jobInstance) 60 | 61 | 62 | -------------------------------------------------------------------------------- /static/assets/js/jquery.flot.resize.min.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for automatically redrawing plots as the placeholder resizes. 2 | 3 | Copyright (c) 2007-2014 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | It works by listening for changes on the placeholder div (through the jQuery 7 | resize event plugin) - if the size changes, it will redraw the plot. 8 | 9 | There are no options. If you need to disable the plugin for some plots, you 10 | can just fix the size of their placeholders. 11 | 12 | */ 13 | !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,c){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,c){a.getPlaceholder().resize(b)}function d(a,c){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); -------------------------------------------------------------------------------- /templates/job_increment_param_record.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block title %}任务增量参数记录{% endblock %} 4 | 5 | {% block main_content %} 6 | 7 |
8 |
9 |
10 | 15 |
16 |
17 | 18 |
19 |
20 |
21 | 22 |
23 |
24 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | {% endblock %} 33 | 34 | {% block script_extend %} 35 | 76 | {% endblock %} -------------------------------------------------------------------------------- /static/assets/js/buttons.colVis.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Column visibility buttons for Buttons and DataTables. 3 | * 2015 SpryMedia Ltd - datatables.net/license 4 | */ 5 | !function(a){"function"==typeof define&&define.amd?define(["jquery","datatables.net","datatables.net-buttons"],function(b){return a(b,window,document)}):"object"==typeof exports?module.exports=function(b,c){return b||(b=window),c&&c.fn.dataTable||(c=require("datatables.net")(b,c).$),c.fn.dataTable.Buttons||require("datatables.net-buttons")(b,c),a(c,b,b.document)}:a(jQuery,window,document)}(function(a,b,c,d){"use strict";var e=a.fn.dataTable;return a.extend(e.ext.buttons,{colvis:function(a,b){return{extend:"collection",text:function(a){return a.i18n("buttons.colvis","Column visibility")},className:"buttons-colvis",buttons:[{extend:"columnsToggle",columns:b.columns}]}},columnsToggle:function(a,b){var c=a.columns(b.columns).indexes().map(function(a){return{extend:"columnToggle",columns:a}}).toArray();return c},columnToggle:function(a,b){return{extend:"columnVisibility",columns:b.columns}},columnsVisibility:function(a,b){var c=a.columns(b.columns).indexes().map(function(a){return{extend:"columnVisibility",columns:a,visibility:b.visibility}}).toArray();return c},columnVisibility:{columns:d,text:function(a,b,c){return c._columnText(a,c.columns)},className:"buttons-columnVisibility",action:function(a,b,c,e){var f=b.columns(e.columns),g=f.visible();f.visible(e.visibility!==d?e.visibility:!(g.length?g[0]:!1))},init:function(a,b,c){var d=this,e=a.column(c.columns);a.on("column-visibility.dt"+c.namespace,function(a,b,e,f){b.bDestroying||e!==c.columns||d.active(f)}).on("column-reorder.dt"+c.namespace,function(b,e,f){if(1===a.columns(c.columns).count()){"number"==typeof c.columns&&(c.columns=f.mapping[c.columns]);var g=a.column(c.columns);d.text(c._columnText(a,c.columns)),d.active(g.visible())}}),this.active(e.visible())},destroy:function(a,b,c){a.off("column-visibility.dt"+c.namespace).off("column-reorder.dt"+c.namespace)},_columnText:function(a,b){var c=a.column(b).index();return a.settings()[0].aoColumns[c].sTitle.replace(/\n/g," ").replace(/<.*?>/g,"").replace(/^\s+|\s+$/g,"")}},colvisRestore:{className:"buttons-colvisRestore",text:function(a){return a.i18n("buttons.colvisRestore","Restore visibility")},init:function(a,b,c){c._visOriginal=a.columns().indexes().map(function(b){return a.column(b).visible()}).toArray()},action:function(a,b,c,d){b.columns().every(function(a){var c=b.colReorder&&b.colReorder.transpose?b.colReorder.transpose(a,"toOriginal"):a;this.visible(d._visOriginal[c])})}},colvisGroup:{className:"buttons-colvisGroup",action:function(a,b,c,d){b.columns(d.show).visible(!0),b.columns(d.hide).visible(!1)},show:[],hide:[]}}),e.Buttons}); -------------------------------------------------------------------------------- /static/assets/css/jquery-ui.custom.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.11.4 - 2015-09-20 2 | * http://jqueryui.com 3 | * Includes: core.css, draggable.css, resizable.css, selectable.css, sortable.css, slider.css 4 | * Copyright 2015 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:after,.ui-helper-clearfix:before{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-autohide .ui-resizable-handle,.ui-resizable-disabled .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} -------------------------------------------------------------------------------- /static/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 .input-group-addon{cursor:pointer}.bootstrap-timepicker .input-group-addon 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 solid transparent;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,.bootstrap-timepicker .dropdown-menu{width:100%}} -------------------------------------------------------------------------------- /static/assets/css/colorbox.min.css: -------------------------------------------------------------------------------- 1 | #cboxWrapper,.cboxPhoto{max-width:none}.cboxIframe,.cboxPhoto{display:block;border:0}#cboxCurrent,#cboxTitle{position:absolute;color:#949494;bottom:4px}#cboxClose:active,#cboxNext:active,#cboxPrevious:active,#cboxSlideshow:active,#colorbox{outline:0}#cboxOverlay,#cboxWrapper,#colorbox{position:absolute;top:0;left:0;z-index:9999;overflow:hidden}#cboxOverlay{position:fixed;width:100%;height:100%;background:url("images/overlay.png.html");opacity:.9;filter:alpha(opacity=90)}#cboxBottomLeft,#cboxMiddleLeft{clear:left}#cboxContent{position:relative;background:#fff;overflow:hidden}#cboxTitle{margin:0;left:0;text-align:center;width:100%}#cboxLoadingGraphic,#cboxLoadingOverlay{position:absolute;top:0;left:0;width:100%;height:100%}.cboxPhoto{float:left;margin:auto;-ms-interpolation-mode:bicubic}.cboxIframe{width:100%;height:100%;padding:0;margin:0}#cboxContent,#cboxLoadedContent,#colorbox{box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box}#cboxTopLeft{width:21px;height:21px;background:url("images/controls.png.html") -101px 0 no-repeat}#cboxTopRight{width:21px;height:21px;background:url("images/controls.png.html") -130px 0 no-repeat}#cboxBottomLeft{width:21px;height:21px;background:url("images/controls.png.html") -101px -29px no-repeat}#cboxBottomRight{width:21px;height:21px;background:url("images/controls.png.html") -130px -29px no-repeat}#cboxMiddleLeft{width:21px;background:url("images/controls.png.html") left top repeat-y}#cboxMiddleRight{width:21px;background:url("images/controls.png.html") right top repeat-y}#cboxTopCenter{height:21px;background:url("images/border.png.html") repeat-x}#cboxBottomCenter{height:21px;background:url("images/border.png.html") 0 -29px repeat-x}.cboxIframe{background:#fff}#cboxError{padding:50px;border:1px solid #ccc}#cboxLoadedContent{overflow:auto;-webkit-overflow-scrolling:touch;margin-bottom:28px}#cboxCurrent{left:58px}#cboxLoadingOverlay{background:url("images/loading_background.png.html") center center no-repeat}#cboxLoadingGraphic{background:url("images/loading.gif.html") center center no-repeat}#cboxClose,#cboxNext,#cboxPrevious,#cboxSlideshow{cursor:pointer;border:0;padding:0;margin:0;overflow:visible;width:auto;background:0 0}#cboxClose,#cboxNext,#cboxPrevious{position:absolute;bottom:0;width:25px;height:25px;text-indent:-9999px}#cboxSlideshow{position:absolute;bottom:4px;right:30px;color:#0092ef}#cboxPrevious{left:0;background:url("images/controls.png.html") -75px 0 no-repeat}#cboxPrevious:hover{background-position:-75px -25px}#cboxNext{left:27px;background:url("images/controls.png.html") -50px 0 no-repeat}#cboxNext:hover{background-position:-50px -25px}#cboxClose{right:0;background:url("images/controls.png.html") -25px 0 no-repeat}#cboxClose:hover{background-position:-25px -25px}.cboxIE #cboxBottomCenter,.cboxIE #cboxBottomLeft,.cboxIE #cboxBottomRight,.cboxIE #cboxMiddleLeft,.cboxIE #cboxMiddleRight,.cboxIE #cboxTopCenter,.cboxIE #cboxTopLeft,.cboxIE #cboxTopRight{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00FFFFFF, endColorstr=#00FFFFFF)} -------------------------------------------------------------------------------- /static/assets/js/autosize.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Autosize 3.0.15 3 | license: MIT 4 | http://www.jacklmoore.com/autosize 5 | */ 6 | !function(a,b){if("function"==typeof define&&define.amd)define(["exports","module"],b);else if("undefined"!=typeof exports&&"undefined"!=typeof module)b(exports,module);else{var c={exports:{}};b(c.exports,c),a.autosize=c.exports}}(this,function(a,b){"use strict";function c(a){function b(){var b=window.getComputedStyle(a,null);n=b.overflowY,"vertical"===b.resize?a.style.resize="none":"both"===b.resize&&(a.style.resize="horizontal"),m="content-box"===b.boxSizing?-(parseFloat(b.paddingTop)+parseFloat(b.paddingBottom)):parseFloat(b.borderTopWidth)+parseFloat(b.borderBottomWidth),isNaN(m)&&(m=0),e()}function c(b){var c=a.style.width;a.style.width="0px",a.offsetWidth,a.style.width=c,n=b,l&&(a.style.overflowY=b),d()}function d(){var b=window.pageYOffset,c=document.body.scrollTop,d=a.style.height;a.style.height="auto";var e=a.scrollHeight+m;return 0===a.scrollHeight?void(a.style.height=d):(a.style.height=e+"px",o=a.clientWidth,document.documentElement.scrollTop=b,void(document.body.scrollTop=c))}function e(){var b=a.style.height;d();var e=window.getComputedStyle(a,null);if(e.height!==a.style.height?"visible"!==n&&c("visible"):"hidden"!==n&&c("hidden"),b!==a.style.height){var f=g("autosize:resized");a.dispatchEvent(f)}}var h=void 0===arguments[1]?{}:arguments[1],i=h.setOverflowX,j=void 0===i?!0:i,k=h.setOverflowY,l=void 0===k?!0:k;if(a&&a.nodeName&&"TEXTAREA"===a.nodeName&&!f.has(a)){var m=null,n=null,o=a.clientWidth,p=function(){a.clientWidth!==o&&e()},q=function(b){window.removeEventListener("resize",p,!1),a.removeEventListener("input",e,!1),a.removeEventListener("keyup",e,!1),a.removeEventListener("autosize:destroy",q,!1),a.removeEventListener("autosize:update",e,!1),f["delete"](a),Object.keys(b).forEach(function(c){a.style[c]=b[c]})}.bind(a,{height:a.style.height,resize:a.style.resize,overflowY:a.style.overflowY,overflowX:a.style.overflowX,wordWrap:a.style.wordWrap});a.addEventListener("autosize:destroy",q,!1),"onpropertychange"in a&&"oninput"in a&&a.addEventListener("keyup",e,!1),window.addEventListener("resize",p,!1),a.addEventListener("input",e,!1),a.addEventListener("autosize:update",e,!1),f.add(a),j&&(a.style.overflowX="hidden",a.style.wordWrap="break-word"),b()}}function d(a){if(a&&a.nodeName&&"TEXTAREA"===a.nodeName){var b=g("autosize:destroy");a.dispatchEvent(b)}}function e(a){if(a&&a.nodeName&&"TEXTAREA"===a.nodeName){var b=g("autosize:update");a.dispatchEvent(b)}}var f="function"==typeof Set?new Set:function(){var a=[];return{has:function(b){return Boolean(a.indexOf(b)>-1)},add:function(b){a.push(b)},"delete":function(b){a.splice(a.indexOf(b),1)}}}(),g=function(a){return new Event(a)};try{new Event("test")}catch(h){g=function(a){var b=document.createEvent("Event");return b.initEvent(a,!0,!1),b}}var i=null;"undefined"==typeof window||"function"!=typeof window.getComputedStyle?(i=function(a){return a},i.destroy=function(a){return a},i.update=function(a){return a}):(i=function(a,b){return a&&Array.prototype.forEach.call(a.length?a:[a],function(a){return c(a,b)}),a},i.destroy=function(a){return a&&Array.prototype.forEach.call(a.length?a:[a],d),a},i.update=function(a){return a&&Array.prototype.forEach.call(a.length?a:[a],e),a}),b.exports=i}); -------------------------------------------------------------------------------- /static/assets/js/bootstrap-wysiwyg.min.js: -------------------------------------------------------------------------------- 1 | /* http://github.com/mindmup/bootstrap-wysiwyg */ 2 | !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); -------------------------------------------------------------------------------- /static/assets/css/bootstrap-colorpicker.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Colorpicker 3 | * http://mjolnic.github.io/bootstrap-colorpicker/ 4 | * 5 | * Originally written by (c) 2012 Stefan Petre 6 | * Licensed under the Apache License v2.0 7 | * http://www.apache.org/licenses/LICENSE-2.0.txt 8 | * 9 | */.colorpicker-saturation{float:left;width:100px;height:100px;cursor:crosshair;background-image:url("../images/bootstrap-colorpicker/saturation.png")}.colorpicker-saturation i{position:absolute;top:0;left:0;display:block;width:5px;height:5px;margin:-4px 0 0 -4px;border:1px solid #000;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-saturation i b{display:block;width:5px;height:5px;border:1px solid #fff;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-alpha,.colorpicker-hue{float:left;width:15px;height:100px;margin-bottom:4px;margin-left:4px;cursor:row-resize}.colorpicker-alpha i,.colorpicker-hue i{position:absolute;top:0;left:0;display:block;width:100%;height:1px;margin-top:-1px;background:#000;border-top:1px solid #fff}.colorpicker-hue{background-image:url("../images/bootstrap-colorpicker/hue.png")}.colorpicker-alpha,.colorpicker-color{background-image:url("../images/bootstrap-colorpicker/alpha.png")}.colorpicker-alpha{display:none}.colorpicker:after,.colorpicker:before{position:absolute;display:inline-block;content:''}.colorpicker-alpha,.colorpicker-hue,.colorpicker-saturation{background-size:contain}.colorpicker{top:0;left:0;z-index:2500;min-width:130px;padding:4px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1}.colorpicker:after,.colorpicker:before{line-height:0}.colorpicker:before{top:-7px;left:6px;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,.2)}.colorpicker:after{clear:both;top:-6px;left:7px;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent}.colorpicker div{position:relative}.colorpicker.colorpicker-with-alpha{min-width:140px}.colorpicker.colorpicker-with-alpha .colorpicker-alpha{display:block}.colorpicker-color{height:10px;margin-top:5px;clear:both;background-position:0 100%}.colorpicker-color div{height:10px}.colorpicker-selectors{display:none;height:10px;margin-top:5px;clear:both}.colorpicker-selectors i{float:left;width:10px;height:10px;cursor:pointer}.colorpicker-selectors i+i{margin-left:3px}.colorpicker-element .add-on i,.colorpicker-element .input-group-addon i{display:inline-block;width:16px;height:16px;vertical-align:text-top;cursor:pointer}.colorpicker.colorpicker-inline{position:relative;z-index:auto;display:inline-block;float:none}.colorpicker.colorpicker-horizontal{width:110px;height:auto;min-width:110px}.colorpicker.colorpicker-horizontal .colorpicker-saturation{margin-bottom:4px}.colorpicker.colorpicker-horizontal .colorpicker-color{width:100px}.colorpicker.colorpicker-horizontal .colorpicker-alpha,.colorpicker.colorpicker-horizontal .colorpicker-hue{float:left;width:100px;height:15px;margin-bottom:4px;margin-left:0;cursor:col-resize}.colorpicker.colorpicker-horizontal .colorpicker-alpha i,.colorpicker.colorpicker-horizontal .colorpicker-hue i{position:absolute;top:0;left:0;display:block;width:1px;height:15px;margin-top:0;background:#fff;border:none}.colorpicker.colorpicker-horizontal .colorpicker-hue{background-image:url("../images/bootstrap-colorpicker/hue-horizontal.png")}.colorpicker.colorpicker-horizontal .colorpicker-alpha{background-image:url("../images/bootstrap-colorpicker/alpha-horizontal.png")}.colorpicker.colorpicker-hidden{display:none}.colorpicker.colorpicker-visible{display:block}.colorpicker-inline.colorpicker-visible{display:inline-block}.colorpicker-right:before{right:6px;left:auto}.colorpicker-right:after{right:7px;left:auto} -------------------------------------------------------------------------------- /static/assets/js/jquery.dataTables.bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /* Set the defaults for DataTables initialisation */ 2 | $.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){return a.preventDefault(),$(a.target).parent().hasClass("disabled")?!1:void("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('