├── .idea ├── inception_web.iml ├── misc.xml ├── modules.xml └── vcs.xml ├── README.md ├── app ├── __init__.py ├── comment.txt ├── form.py ├── models.py ├── mysqltuner.pl ├── static │ ├── mysql_db_design_guide.docx │ ├── resource │ │ ├── css │ │ │ ├── animate.min.css │ │ │ ├── bootstrap.min.css │ │ │ ├── buttons.bootstrap.min.css │ │ │ ├── custom.min.css │ │ │ ├── dataTables.bootstrap.min.css │ │ │ ├── fixedHeader.bootstrap.min.css │ │ │ ├── font-awesome.min.css │ │ │ ├── green.css │ │ │ ├── nprogress.css │ │ │ ├── responsive.bootstrap.min.css │ │ │ └── scroller.bootstrap.min.css │ │ ├── fonts │ │ │ ├── fontawesome-webfont.woff2 │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ │ ├── bootstrap.min.js │ │ │ ├── buttons.bootstrap.min.js │ │ │ ├── buttons.flash.min.js │ │ │ ├── buttons.html5.min.js │ │ │ ├── buttons.print.min.js │ │ │ ├── custom.min.js │ │ │ ├── dataTables.bootstrap.min.js │ │ │ ├── dataTables.buttons.min.js │ │ │ ├── dataTables.fixedHeader.min.js │ │ │ ├── dataTables.keyTable.min.js │ │ │ ├── dataTables.responsive.min.js │ │ │ ├── dataTables.scroller.min.js │ │ │ ├── echarts.min.js │ │ │ ├── fastclick.js │ │ │ ├── icheck.min.js │ │ │ ├── jquery.dataTables.min.js │ │ │ ├── jquery.min.js │ │ │ ├── jszip.min.js │ │ │ ├── macarons.js │ │ │ ├── nprogress.js │ │ │ ├── pdfmake.min.js │ │ │ ├── responsive.bootstrap.js │ │ │ └── vfs_fonts.js │ └── touxiang.jpg ├── templates │ ├── admin_chart.html │ ├── audit_chart.html │ ├── audit_work.html │ ├── audit_work_assign.html │ ├── audit_work_pending.html │ ├── audit_work_timer.html │ ├── audit_work_timer_view.html │ ├── base.html │ ├── dashboard.html │ ├── dbreport.html │ ├── dbreport_view.html │ ├── dev_chart.html │ ├── dev_work.html │ ├── dev_work_create.html │ ├── dev_work_update.html │ ├── login.html │ ├── modules.html │ ├── mysql_db.html │ ├── mysql_db_create.html │ ├── mysql_db_update.html │ ├── passwd.html │ ├── slowlog.html │ ├── sqladvisor_check.html │ ├── user.html │ ├── user_create.html │ ├── user_db_alloc.html │ ├── user_update.html │ ├── view_slowlog.html │ └── work_view.html ├── utils.py └── views.py ├── config_example.py ├── debug.sh ├── doc └── images │ ├── SQLAdvisor.png │ ├── 发起sql工单.png │ ├── 工单图表.png │ ├── 工单处理.png │ ├── 工单查询.png │ ├── 待审核工单.png │ ├── 登陆页.png │ ├── 管理员主页.png │ └── 配置报告.png ├── requirements.txt ├── run.py ├── start.sh └── stop.sh /.idea/inception_web.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 16 | 17 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Inception_web 2 | 本系统是MySQL自动化管理工具,配合Inception使用,基于archer进行二次开发,进行了一些补充优化。 3 | ## 功能说明: 4 | - __SQL自主审核__ 5 | - __自动审核+人工审核__ 6 | - __定时执行SQL__ 7 | - __主副人工审核__(可配置) 8 | - __回滚sql下载__ 9 | - __数据库配置__ 10 | - __用户权限配置__ 11 | - __用户分配数据库权限__ 12 | - __工单查询管理__ 13 | - __工单邮件通知__ 14 | - __查看慢查询__ 15 | - __MySQLTuner生成配置分析报告__(需安装perl) 16 | - __SQLAdvisor语句优化功能__(安装模块) 17 | ## 配置文件: 18 | config.py 19 | ## 安装配置: 20 | 要求:python2.7
21 | 建议系统环境:CentOS 7/Ubuntu 14+ 22 | 23 | 1.安装MySQL 5.6+数据库,用于存放系统数据和回滚sql。
24 | 建立数据库和用户:
25 | create database inception_web character set utf8;
26 | grant all privileges on \*.\* to inception_web@'%' identified by 'inception_web';
27 | flush privileges;
28 | 29 | 2.安装Inception(参考文档:http://mysql-inception.github.io/inception-document/install/ )
30 | inc.cnf使用之前创建的mysql主机帐号密码
31 | 32 | 3.下载系统源码
33 | git clone https://github.com/496080199/inception_web.git
34 | 或使用zip包下载
35 | 36 | 3.安装python2.7依赖
37 | 安装pip工具,具体网上搜索(下载配置加速可参见https://pypi-mirrors.org/ )
38 | cd inception_web
39 | pip install -r requirements.txt
40 | 41 | 4.配置修改
42 | 复制config_example.py为config.py
43 | 根据自己的环境进行相应修改config.py中参数
44 | 注:查看慢查询需设置mysql的参数log_output=table将慢查询记录输出到mysql库的slow_log表中 45 | 46 | 5.启动运行
47 | 测试环境:
48 | chmod +x debug.sh
49 | ./debug.sh
50 | 51 | 生产环境:
52 | chmod +x start.sh stop.sh
53 | pip install gunicorn
54 | 启动:./start.sh
55 | 关闭:./stop.sh
56 | 57 | 6.访问
58 | 59 | http://(部署服务器IP):5000/login
60 | 初始帐号密码:admin/admin
61 | 注:防火墙端口5000需要放开
62 | 63 | 7.依次添加数据库,开发人员(分配数据库),审核人员,开始工作。 64 | 65 | 8.模块安装
66 | SQLAdvisor安装:
67 | 下载 https://github.com/Meituan-Dianping/SQLAdvisor/archive/master.zip 压缩包放至本系统根目录下,
68 | 即可通过模块管理进行安装,安装需要几分钟,请耐心等待后刷新页面看到。
69 | 70 |
71 |
72 | -------有更多idea欢迎和我一起交流分享,谢谢!我的QQ:496080199
73 |
74 | 设计原理来源于archer,请大家多关注
75 | https://github.com/jly8866/archer
76 |
77 | ## 系统截图:
78 | 1. 发起sql工单页:
79 | 80 | ![image](https://github.com/496080199/inception_web/raw/master/doc/images/%E5%8F%91%E8%B5%B7sql%E5%B7%A5%E5%8D%95.png) 81 |
82 | 83 | 2. 工单图表页:
84 | 85 | ![image](https://github.com/496080199/inception_web/raw/master/doc/images/%E5%B7%A5%E5%8D%95%E5%9B%BE%E8%A1%A8.png) 86 |
87 | 88 | 3. 工单处理页:
89 | 90 | ![image](https://github.com/496080199/inception_web/raw/master/doc/images/%E5%B7%A5%E5%8D%95%E5%A4%84%E7%90%86.png) 91 |
92 | 93 | 4. 工单查询页:
94 | 95 | ![image](https://github.com/496080199/inception_web/raw/master/doc/images/%E5%B7%A5%E5%8D%95%E6%9F%A5%E8%AF%A2.png) 96 |
97 | 98 | 5. 待审核工单页:
99 | 100 | ![image](https://github.com/496080199/inception_web/raw/master/doc/images/%E5%BE%85%E5%AE%A1%E6%A0%B8%E5%B7%A5%E5%8D%95.png) 101 |
102 | 103 | 6. 登陆页:
104 | 105 | ![image](https://github.com/496080199/inception_web/raw/master/doc/images/%E7%99%BB%E9%99%86%E9%A1%B5.png) 106 |
107 | 108 | 7. 管理员主页:
109 | 110 | ![image](https://github.com/496080199/inception_web/raw/master/doc/images/%E7%AE%A1%E7%90%86%E5%91%98%E4%B8%BB%E9%A1%B5.png) 111 |
112 | 113 | 8. mysqltuner配置分析报告:
114 | 115 | ![image](https://github.com/496080199/inception_web/raw/master/doc/images/%E9%85%8D%E7%BD%AE%E6%8A%A5%E5%91%8A.png) 116 |
117 | 118 | 9. SQLAdvisor语句优化:
119 | 120 | ![image](https://github.com/496080199/inception_web/raw/master/doc/images/SQLAdvisor.png) 121 |
122 | 123 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_mail import Mail 3 | from flask_sqlalchemy import SQLAlchemy 4 | from flask_principal import Principal, identity_loaded, RoleNeed, UserNeed 5 | from flask_login import LoginManager, current_user 6 | from werkzeug.security import generate_password_hash 7 | from datetime import timedelta 8 | 9 | 10 | app = Flask(__name__) 11 | app.config.from_object('config') 12 | 13 | # load the extension 14 | principals = Principal(app) 15 | 16 | login_manager = LoginManager(app) 17 | login_manager.login_view = 'login' 18 | login_manager.session_protection = 'strong' 19 | login_manager.remember_cookie_duration = timedelta(minutes=15) 20 | 21 | mail = Mail(app) 22 | db = SQLAlchemy(app) 23 | 24 | 25 | from app.models import * 26 | 27 | db.create_all() 28 | 29 | 30 | admin=User() 31 | admin.name='admin' 32 | admin.email='admin@admin.cn' 33 | admin.hash_pass=generate_password_hash('admin') 34 | admin.role='admin' 35 | user=User.query.filter(User.name == 'admin').first() 36 | if not user: 37 | db.session.add(admin) 38 | db.session.commit() 39 | 40 | 41 | @login_manager.user_loader 42 | def load_user(id): 43 | return User.query.get(id) 44 | @login_manager.unauthorized_handler 45 | def unauthorized(): 46 | return redirect('/login') 47 | 48 | @identity_loaded.connect_via(app) 49 | def on_identity_loaded(sender, identity): 50 | 51 | identity.user = current_user 52 | 53 | 54 | if hasattr(current_user, 'id'): 55 | identity.provides.add(UserNeed(current_user.id)) 56 | 57 | 58 | if hasattr(current_user, 'role'): 59 | identity.provides.add(RoleNeed(current_user.role)) 60 | 61 | 62 | # Setup Flask-User 63 | 64 | 65 | from app.views import * -------------------------------------------------------------------------------- /app/comment.txt: -------------------------------------------------------------------------------- 1 | 工单状态说明: 2 | 3 | 0 正常结束 4 | 1 待人工审核 5 | 2 自动审核失败 6 | 3 执行中 7 | 4 执行异常 8 | 5 开发人中止 9 | 6 审核人中止 10 | 7 管理员中止 11 | 8 审核人驳回 -------------------------------------------------------------------------------- /app/form.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from flask_wtf import FlaskForm 4 | from wtforms import StringField, IntegerField, PasswordField, BooleanField, TextAreaField, DateField, DateTimeField 5 | from wtforms.validators import DataRequired 6 | 7 | class LoginForm(FlaskForm): 8 | name = StringField(u'用户名', validators=[DataRequired()]) 9 | passwd = PasswordField(u'密码', validators=[DataRequired()]) 10 | class PasswdForm(FlaskForm): 11 | old_pass = PasswordField(u'旧密码', validators=[DataRequired()]) 12 | new_pass = PasswordField(u'新密码', validators=[DataRequired()]) 13 | rep_pass = PasswordField(u'重复密码', validators=[DataRequired()]) 14 | class MysqlDbForm(FlaskForm): 15 | name = StringField(u'名称', validators=[DataRequired()]) 16 | host = StringField(u'主机', validators=[DataRequired()]) 17 | port = IntegerField(u'端口', validators=[DataRequired()]) 18 | user = StringField(u'用户名', validators=[DataRequired()]) 19 | password = PasswordField(u'密码') 20 | class UserForm(FlaskForm): 21 | name = StringField(u'用户名', validators=[DataRequired()]) 22 | passwd = PasswordField(u'密码', validators=[DataRequired()]) 23 | role = StringField(u'角色', validators=[DataRequired()]) 24 | email = StringField(u'邮箱', validators=[DataRequired()]) 25 | class UserDbForm(FlaskForm): 26 | db = IntegerField(u'数据库', validators=[DataRequired()]) 27 | class WorkForm(FlaskForm): 28 | name = StringField(u'工单名') 29 | db_config = StringField(u'数据库', validators=[DataRequired()]) 30 | backup = BooleanField(u'备份', validators=[DataRequired()]) 31 | audit = StringField(u'审核人', validators=[DataRequired()]) 32 | sql_content = TextAreaField(u'sql内容', validators=[DataRequired()]) 33 | class WorkAssignForm(FlaskForm): 34 | audit = StringField(u'审核人', validators=[DataRequired()]) 35 | class ReportForm(FlaskForm): 36 | mem = StringField(u'内存大小', validators=[DataRequired()]) 37 | -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from app import db 3 | from datetime import datetime 4 | 5 | 6 | 7 | dbs = db.Table('dbs', 8 | db.Column('user_id', db.Integer, db.ForeignKey('user.id')), 9 | db.Column('dbconfig_id', db.Integer, db.ForeignKey('dbconfig.id')) 10 | ) 11 | 12 | class User(db.Model): 13 | id = db.Column(db.Integer, primary_key = True) 14 | name = db.Column(db.String(64), unique = True) 15 | email = db.Column(db.String(120)) 16 | role = db.Column(db.String(120)) 17 | srole =db.Column(db.Integer, default=0) 18 | hash_pass = db.Column(db.String(200)) 19 | dbs = db.relationship('Dbconfig', secondary=dbs, backref=db.backref('users', lazy='dynamic')) 20 | 21 | def is_authenticated(self): 22 | return False 23 | 24 | def is_active(self): 25 | return False 26 | 27 | def is_anonymous(self): 28 | return False 29 | 30 | def get_id(self): 31 | try: 32 | return unicode(self.id) # python 2 33 | except NameError: 34 | return str(self.id) # python 3 35 | 36 | def __repr__(self): 37 | return '' % (self.name) 38 | class Dbconfig(db.Model): 39 | id = db.Column(db.Integer, primary_key=True) 40 | name = db.Column(db.String(100), unique = True ) 41 | host = db.Column(db.String(200),) 42 | port = db.Column(db.Integer,default=3306) 43 | user = db.Column(db.String(100)) 44 | password = db.Column(db.String(300)) 45 | create_time = db.Column(db.DateTime, default=datetime.now()) 46 | update_time = db.Column(db.DateTime, default=datetime.now()) 47 | 48 | 49 | 50 | class Work(db.Model): 51 | id = db.Column(db.Integer, primary_key=True) 52 | name = db.Column(db.String(128), unique=True) 53 | dev = db.Column(db.String(64)) 54 | audit = db.Column(db.String(64)) 55 | srole = db.Column(db.Integer, default=0) 56 | sql_content = db.Column(db.TEXT(16777215)) 57 | db_config = db.Column(db.String(128)) 58 | backup = db.Column(db.Boolean, default=True) 59 | status = db.Column(db.Integer, nullable=False) 60 | create_time = db.Column(db.DateTime, default=datetime.now()) 61 | finish_time = db.Column(db.DateTime) 62 | man_review_time = db.Column(db.DateTime) 63 | auto_review = db.Column(db.TEXT(16777215)) 64 | execute_result = db.Column(db.TEXT(16777215)) 65 | timer = db.Column(db.DateTime) 66 | class Report(db.Model): 67 | id = db.Column(db.Integer, primary_key=True) 68 | db_name = db.Column(db.String(64)) 69 | mem = db.Column(db.Integer) 70 | create_time = db.Column(db.DateTime, default=datetime.now()) 71 | report_content = db.Column(db.TEXT(16777215)) 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /app/static/mysql_db_design_guide.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/app/static/mysql_db_design_guide.docx -------------------------------------------------------------------------------- /app/static/resource/css/buttons.bootstrap.min.css: -------------------------------------------------------------------------------- 1 | div.dt-button-info{position:fixed;top:50%;left:50%;width:400px;margin-top:-100px;margin-left:-200px;background-color:white;border:2px solid #111;box-shadow:3px 3px 8px rgba(0,0,0,0.3);border-radius:3px;text-align:center;z-index:21}div.dt-button-info h2{padding:0.5em;margin:0;font-weight:normal;border-bottom:1px solid #ddd;background-color:#f3f3f3}div.dt-button-info>div{padding:1em}ul.dt-button-collection.dropdown-menu{display:block;z-index:2002;-webkit-column-gap:8px;-moz-column-gap:8px;-ms-column-gap:8px;-o-column-gap:8px;column-gap:8px}ul.dt-button-collection.dropdown-menu.fixed{position:fixed;top:50%;left:50%;margin-left:-75px;border-radius:0}ul.dt-button-collection.dropdown-menu.fixed.two-column{margin-left:-150px}ul.dt-button-collection.dropdown-menu.fixed.three-column{margin-left:-225px}ul.dt-button-collection.dropdown-menu.fixed.four-column{margin-left:-300px}ul.dt-button-collection.dropdown-menu>*{-webkit-column-break-inside:avoid;break-inside:avoid}ul.dt-button-collection.dropdown-menu.two-column{width:300px;padding-bottom:1px;-webkit-column-count:2;-moz-column-count:2;-ms-column-count:2;-o-column-count:2;column-count:2}ul.dt-button-collection.dropdown-menu.three-column{width:450px;padding-bottom:1px;-webkit-column-count:3;-moz-column-count:3;-ms-column-count:3;-o-column-count:3;column-count:3}ul.dt-button-collection.dropdown-menu.four-column{width:600px;padding-bottom:1px;-webkit-column-count:4;-moz-column-count:4;-ms-column-count:4;-o-column-count:4;column-count:4}div.dt-button-background{position:fixed;top:0;left:0;width:100%;height:100%;z-index:2001}@media screen and (max-width: 767px){div.dt-buttons{float:none;width:100%;text-align:center;margin-bottom:0.5em}div.dt-buttons a.btn{float:none}} 2 | -------------------------------------------------------------------------------- /app/static/resource/css/dataTables.bootstrap.min.css: -------------------------------------------------------------------------------- 1 | table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important;border-collapse:separate !important}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:75px;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:8px;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:8px;right:8px;display:block;font-family:'Glyphicons Halflings';opacity:0.5}table.dataTable thead .sorting:after{opacity:0.2;content:"\e150"}table.dataTable thead .sorting_asc:after{content:"\e155"}table.dataTable thead .sorting_desc:after{content:"\e156"}table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{color:#eee}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table thead .sorting:after,div.dataTables_scrollBody table thead .sorting_asc:after,div.dataTables_scrollBody table thead .sorting_desc:after{display:none}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-condensed>thead>tr>th{padding-right:20px}table.dataTable.table-condensed .sorting:after,table.dataTable.table-condensed .sorting_asc:after,table.dataTable.table-condensed .sorting_desc:after{top:6px;right:6px}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-child{padding-right:0} 2 | -------------------------------------------------------------------------------- /app/static/resource/css/fixedHeader.bootstrap.min.css: -------------------------------------------------------------------------------- 1 | table.dataTable.fixedHeader-floating,table.dataTable.fixedHeader-locked{background-color:white;margin-top:0 !important;margin-bottom:0 !important}table.dataTable.fixedHeader-floating{position:fixed !important}table.dataTable.fixedHeader-locked{position:absolute !important}@media print{table.fixedHeader-floating{display:none}} 2 | -------------------------------------------------------------------------------- /app/static/resource/css/green.css: -------------------------------------------------------------------------------- 1 | /* iCheck plugin Flat skin, green 2 | ----------------------------------- */ 3 | .icheckbox_flat-green, 4 | .iradio_flat-green { 5 | display: inline-block; 6 | *display: inline; 7 | vertical-align: middle; 8 | margin: 0; 9 | padding: 0; 10 | width: 20px; 11 | height: 20px; 12 | background: url(green.png) no-repeat; 13 | border: none; 14 | cursor: pointer; 15 | } 16 | 17 | .icheckbox_flat-green { 18 | background-position: 0 0; 19 | } 20 | .icheckbox_flat-green.checked { 21 | background-position: -22px 0; 22 | } 23 | .icheckbox_flat-green.disabled { 24 | background-position: -44px 0; 25 | cursor: default; 26 | } 27 | .icheckbox_flat-green.checked.disabled { 28 | background-position: -66px 0; 29 | } 30 | 31 | .iradio_flat-green { 32 | background-position: -88px 0; 33 | } 34 | .iradio_flat-green.checked { 35 | background-position: -110px 0; 36 | } 37 | .iradio_flat-green.disabled { 38 | background-position: -132px 0; 39 | cursor: default; 40 | } 41 | .iradio_flat-green.checked.disabled { 42 | background-position: -154px 0; 43 | } 44 | 45 | /* HiDPI support */ 46 | @media (-o-min-device-pixel-ratio: 5/4), (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { 47 | .icheckbox_flat-green, 48 | .iradio_flat-green { 49 | background-image: url(green@2x.png); 50 | -webkit-background-size: 176px 22px; 51 | background-size: 176px 22px; 52 | } 53 | } -------------------------------------------------------------------------------- /app/static/resource/css/nprogress.css: -------------------------------------------------------------------------------- 1 | /* Make clicks pass-through */ 2 | #nprogress { 3 | pointer-events: none; 4 | } 5 | 6 | #nprogress .bar { 7 | background: #29d; 8 | 9 | position: fixed; 10 | z-index: 1031; 11 | top: 0; 12 | left: 0; 13 | 14 | width: 100%; 15 | height: 2px; 16 | } 17 | 18 | /* Fancy blur effect */ 19 | #nprogress .peg { 20 | display: block; 21 | position: absolute; 22 | right: 0px; 23 | width: 100px; 24 | height: 100%; 25 | box-shadow: 0 0 10px #29d, 0 0 5px #29d; 26 | opacity: 1.0; 27 | 28 | -webkit-transform: rotate(3deg) translate(0px, -4px); 29 | -ms-transform: rotate(3deg) translate(0px, -4px); 30 | transform: rotate(3deg) translate(0px, -4px); 31 | } 32 | 33 | /* Remove these to get rid of the spinner */ 34 | #nprogress .spinner { 35 | display: block; 36 | position: fixed; 37 | z-index: 1031; 38 | top: 15px; 39 | right: 15px; 40 | } 41 | 42 | #nprogress .spinner-icon { 43 | width: 18px; 44 | height: 18px; 45 | box-sizing: border-box; 46 | 47 | border: solid 2px transparent; 48 | border-top-color: #29d; 49 | border-left-color: #29d; 50 | border-radius: 50%; 51 | 52 | -webkit-animation: nprogress-spinner 400ms linear infinite; 53 | animation: nprogress-spinner 400ms linear infinite; 54 | } 55 | 56 | .nprogress-custom-parent { 57 | overflow: hidden; 58 | position: relative; 59 | } 60 | 61 | .nprogress-custom-parent #nprogress .spinner, 62 | .nprogress-custom-parent #nprogress .bar { 63 | position: absolute; 64 | } 65 | 66 | @-webkit-keyframes nprogress-spinner { 67 | 0% { -webkit-transform: rotate(0deg); } 68 | 100% { -webkit-transform: rotate(360deg); } 69 | } 70 | @keyframes nprogress-spinner { 71 | 0% { transform: rotate(0deg); } 72 | 100% { transform: rotate(360deg); } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /app/static/resource/css/responsive.bootstrap.min.css: -------------------------------------------------------------------------------- 1 | table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty{cursor:default !important}table.dataTable.dtr-inline.collapsed>tbody>tr>td.child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th.child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty:before{display:none !important}table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child,table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child{position:relative;padding-left:30px;cursor:pointer}table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child:before{top:9px;left:4px;height:14px;width:14px;display:block;position:absolute;color:white;border:2px solid white;border-radius:14px;box-shadow:0 0 3px #444;box-sizing:content-box;text-align:center;font-family:'Courier New', Courier, monospace;line-height:14px;content:'+';background-color:#337ab7}table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td:first-child:before,table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th:first-child:before{content:'-';background-color:#d33333}table.dataTable.dtr-inline.collapsed>tbody>tr.child td:before{display:none}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td:first-child,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th:first-child{padding-left:27px}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td:first-child:before,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th:first-child:before{top:5px;left:4px;height:14px;width:14px;border-radius:14px;line-height:14px;text-indent:3px}table.dataTable.dtr-column>tbody>tr>td.control,table.dataTable.dtr-column>tbody>tr>th.control{position:relative;cursor:pointer}table.dataTable.dtr-column>tbody>tr>td.control:before,table.dataTable.dtr-column>tbody>tr>th.control:before{top:50%;left:50%;height:16px;width:16px;margin-top:-10px;margin-left:-10px;display:block;position:absolute;color:white;border:2px solid white;border-radius:14px;box-shadow:0 0 3px #444;box-sizing:content-box;text-align:center;font-family:'Courier New', Courier, monospace;line-height:14px;content:'+';background-color:#337ab7}table.dataTable.dtr-column>tbody>tr.parent td.control:before,table.dataTable.dtr-column>tbody>tr.parent th.control:before{content:'-';background-color:#d33333}table.dataTable>tbody>tr.child{padding:0.5em 1em}table.dataTable>tbody>tr.child:hover{background:transparent !important}table.dataTable>tbody>tr.child ul{display:inline-block;list-style-type:none;margin:0;padding:0}table.dataTable>tbody>tr.child ul li{border-bottom:1px solid #efefef;padding:0.5em 0}table.dataTable>tbody>tr.child ul li:first-child{padding-top:0}table.dataTable>tbody>tr.child ul li:last-child{border-bottom:none}table.dataTable>tbody>tr.child span.dtr-title{display:inline-block;min-width:75px;font-weight:bold}div.dtr-modal{position:fixed;box-sizing:border-box;top:0;left:0;height:100%;width:100%;z-index:100;padding:10em 1em}div.dtr-modal div.dtr-modal-display{position:absolute;top:0;left:0;bottom:0;right:0;width:50%;height:50%;overflow:auto;margin:auto;z-index:102;overflow:auto;background-color:#f5f5f7;border:1px solid black;border-radius:0.5em;box-shadow:0 12px 30px rgba(0,0,0,0.6)}div.dtr-modal div.dtr-modal-content{position:relative;padding:1em}div.dtr-modal div.dtr-modal-close{position:absolute;top:6px;right:6px;width:22px;height:22px;border:1px solid #eaeaea;background-color:#f9f9f9;text-align:center;border-radius:3px;cursor:pointer;z-index:12}div.dtr-modal div.dtr-modal-close:hover{background-color:#eaeaea}div.dtr-modal div.dtr-modal-background{position:fixed;top:0;left:0;right:0;bottom:0;z-index:101;background:rgba(0,0,0,0.6)}@media screen and (max-width: 767px){div.dtr-modal div.dtr-modal-display{width:95%}}div.dtr-bs-modal table.table tr:first-child td{border-top:none} 2 | -------------------------------------------------------------------------------- /app/static/resource/css/scroller.bootstrap.min.css: -------------------------------------------------------------------------------- 1 | div.DTS{display:block !important}div.DTS tbody th,div.DTS tbody td{white-space:nowrap}div.DTS div.DTS_Loading{z-index:1}div.DTS div.dataTables_scrollBody{background:repeating-linear-gradient(45deg, #edeeff, #edeeff 10px, #fff 10px, #fff 20px)}div.DTS div.dataTables_scrollBody table{z-index:2}div.DTS div.dataTables_paginate,div.DTS div.dataTables_length{display:none}div.DTS tbody tr.even{background-color:white} 2 | -------------------------------------------------------------------------------- /app/static/resource/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/app/static/resource/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /app/static/resource/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/app/static/resource/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/static/resource/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/app/static/resource/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/static/resource/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/app/static/resource/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /app/static/resource/js/buttons.bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Bootstrap integration for DataTables' Buttons 3 | ©2016 SpryMedia Ltd - datatables.net/license 4 | */ 5 | (function(c){"function"===typeof define&&define.amd?define(["jquery","datatables.net-bs","datatables.net-buttons"],function(a){return c(a,window,document)}):"object"===typeof exports?module.exports=function(a,b){a||(a=window);if(!b||!b.fn.dataTable)b=require("datatables.net-bs")(a,b).$;b.fn.dataTable.Buttons||require("datatables.net-buttons")(a,b);return c(b,a,a.document)}:c(jQuery,window,document)})(function(c){var a=c.fn.dataTable;c.extend(!0,a.Buttons.defaults,{dom:{container:{className:"dt-buttons btn-group"}, 6 | button:{className:"btn btn-default"},collection:{tag:"ul",className:"dt-button-collection dropdown-menu",button:{tag:"li",className:"dt-button"},buttonLiner:{tag:"a",className:""}}}});a.ext.buttons.collection.text=function(a){return a.i18n("buttons.collection",'Collection ')};return a.Buttons}); 7 | -------------------------------------------------------------------------------- /app/static/resource/js/buttons.print.min.js: -------------------------------------------------------------------------------- 1 | (function(e){"function"===typeof define&&define.amd?define(["jquery","datatables.net","datatables.net-buttons"],function(d){return e(d,window,document)}):"object"===typeof exports?module.exports=function(d,a){d||(d=window);if(!a||!a.fn.dataTable)a=require("datatables.net")(d,a).$;a.fn.dataTable.Buttons||require("datatables.net-buttons")(d,a);return e(a,d,d.document)}:e(jQuery,window,document)})(function(e,d,a){var i=e.fn.dataTable,g=a.createElement("a");i.ext.buttons.print={className:"buttons-print", 2 | text:function(c){return c.i18n("buttons.print","Print")},action:function(c,b,a,f){c=b.buttons.exportData(f.exportOptions);a=function(c,a){for(var b="",d=0,e=c.length;d"+c[d]+"";return b+""};b='';f.header&&(b+=""+a(c.header,"th")+"");for(var b=b+"",j=0,i=c.body.length;j";f.footer&&c.footer&&(b+=""+a(c.footer,"th")+"");var h=d.open("",""),c=f.title; 3 | "function"===typeof c&&(c=c());-1!==c.indexOf("*")&&(c=c.replace("*",e("title").text()));h.document.close();var k=""+c+"";e("style, link").each(function(){var c=k,b=e(this).clone()[0],a;"link"===b.nodeName.toLowerCase()&&(g.href=b.href,a=g.host,-1===a.indexOf("/")&&0!==g.pathname.indexOf("/")&&(a+="/"),b.href=g.protocol+"//"+a+g.pathname+g.search);k=c+b.outerHTML});h.document.head.innerHTML=k;h.document.body.innerHTML="

"+c+"

"+f.message+"
"+b;f.customize&&f.customize(h); 4 | setTimeout(function(){f.autoPrint&&(h.print(),h.close())},250)},title:"*",message:"",exportOptions:{},header:!0,footer:!1,autoPrint:!0,customize:null};return i.Buttons}); 5 | -------------------------------------------------------------------------------- /app/static/resource/js/dataTables.bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | DataTables Bootstrap 3 integration 3 | ©2011-2015 SpryMedia Ltd - datatables.net/license 4 | */ 5 | (function(b){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(a){return b(a,window,document)}):"object"===typeof exports?module.exports=function(a,d){a||(a=window);if(!d||!d.fn.dataTable)d=require("datatables.net")(a,d).$;return b(d,a,a.document)}:b(jQuery,window,document)})(function(b,a,d){var f=b.fn.dataTable;b.extend(!0,f.defaults,{dom:"<'row'<'col-sm-6'l><'col-sm-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-5'i><'col-sm-7'p>>",renderer:"bootstrap"});b.extend(f.ext.classes, 6 | {sWrapper:"dataTables_wrapper form-inline dt-bootstrap",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm",sProcessing:"dataTables_processing panel panel-default"});f.ext.renderer.pageButton.bootstrap=function(a,h,r,m,j,n){var o=new f.Api(a),s=a.oClasses,k=a.oLanguage.oPaginate,t=a.oLanguage.oAria.paginate||{},e,g,p=0,q=function(d,f){var l,h,i,c,m=function(a){a.preventDefault();!b(a.currentTarget).hasClass("disabled")&&o.page()!=a.data.action&&o.page(a.data.action).draw("page")}; 7 | l=0;for(h=f.length;l",{"class":s.sPageButton+" "+g,id:0===r&&"string"===typeof c?a.sTableId+"_"+c:null}).append(b("",{href:"#", 8 | "aria-controls":a.sTableId,"aria-label":t[c],"data-dt-idx":p,tabindex:a.iTabIndex}).html(e)).appendTo(d),a.oApi._fnBindAction(i,{action:c},m),p++)}},i;try{i=b(h).find(d.activeElement).data("dt-idx")}catch(u){}q(b(h).empty().html('
    ').children("ul"),m);i&&b(h).find("[data-dt-idx="+i+"]").focus()};return f}); 9 | -------------------------------------------------------------------------------- /app/static/resource/js/dataTables.fixedHeader.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | FixedHeader 3.1.2 3 | ©2009-2016 SpryMedia Ltd - datatables.net/license 4 | */ 5 | (function(d){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(g){return d(g,window,document)}):"object"===typeof exports?module.exports=function(g,h){g||(g=window);if(!h||!h.fn.dataTable)h=require("datatables.net")(g,h).$;return d(h,g,g.document)}:d(jQuery,window,document)})(function(d,g,h,k){var j=d.fn.dataTable,l=0,i=function(b,a){if(!(this instanceof i))throw"FixedHeader must be initialised with the 'new' keyword.";!0===a&&(a={});b=new j.Api(b);this.c=d.extend(!0, 6 | {},i.defaults,a);this.s={dt:b,position:{theadTop:0,tbodyTop:0,tfootTop:0,tfootBottom:0,width:0,left:0,tfootHeight:0,theadHeight:0,windowHeight:d(g).height(),visible:!0},headerMode:null,footerMode:null,autoWidth:b.settings()[0].oFeatures.bAutoWidth,namespace:".dtfc"+l++,scrollLeft:{header:-1,footer:-1},enable:!0};this.dom={floatingHeader:null,thead:d(b.table().header()),tbody:d(b.table().body()),tfoot:d(b.table().footer()),header:{host:null,floating:null,placeholder:null},footer:{host:null,floating:null, 7 | placeholder:null}};this.dom.header.host=this.dom.thead.parent();this.dom.footer.host=this.dom.tfoot.parent();var e=b.settings()[0];if(e._fixedHeader)throw"FixedHeader already initialised on table "+e.nTable.id;e._fixedHeader=this;this._constructor()};d.extend(i.prototype,{enable:function(b){this.s.enable=b;this.c.header&&this._modeChange("in-place","header",!0);this.c.footer&&this.dom.tfoot.length&&this._modeChange("in-place","footer",!0);this.update()},headerOffset:function(b){b!==k&&(this.c.headerOffset= 8 | b,this.update());return this.c.headerOffset},footerOffset:function(b){b!==k&&(this.c.footerOffset=b,this.update());return this.c.footerOffset},update:function(){this._positions();this._scroll(!0)},_constructor:function(){var b=this,a=this.s.dt;d(g).on("scroll"+this.s.namespace,function(){b._scroll()}).on("resize"+this.s.namespace,function(){b.s.position.windowHeight=d(g).height();b.update()});var e=d(".fh-fixedHeader");!this.c.headerOffset&&e.length&&(this.c.headerOffset=e.outerHeight());e=d(".fh-fixedFooter"); 9 | !this.c.footerOffset&&e.length&&(this.c.footerOffset=e.outerHeight());a.on("column-reorder.dt.dtfc column-visibility.dt.dtfc draw.dt.dtfc column-sizing.dt.dtfc",function(){b.update()});a.on("destroy.dtfc",function(){a.off(".dtfc");d(g).off(b.s.namespace)});this._positions();this._scroll()},_clone:function(b,a){var e=this.s.dt,c=this.dom[b],f="header"===b?this.dom.thead:this.dom.tfoot;!a&&c.floating?c.floating.removeClass("fixedHeader-floating fixedHeader-locked"):(c.floating&&(c.placeholder.remove(), 10 | this._unsize(b),c.floating.children().detach(),c.floating.remove()),c.floating=d(e.table().node().cloneNode(!1)).css("table-layout","fixed").removeAttr("id").append(f).appendTo("body"),c.placeholder=f.clone(!1),c.host.prepend(c.placeholder),this._matchWidths(c.placeholder,c.floating))},_matchWidths:function(b,a){var e=function(a){return d(a,b).map(function(){return d(this).width()}).toArray()},c=function(b,c){d(b,a).each(function(a){d(this).css({width:c[a],minWidth:c[a]})})},f=e("th"),e=e("td");c("th", 11 | f);c("td",e)},_unsize:function(b){var a=this.dom[b].floating;a&&("footer"===b||"header"===b&&!this.s.autoWidth)?d("th, td",a).css({width:"",minWidth:""}):a&&"header"===b&&d("th, td",a).css("min-width","")},_horizontal:function(b,a){var e=this.dom[b],c=this.s.position,d=this.s.scrollLeft;e.floating&&d[b]!==a&&(e.floating.css("left",c.left-a),d[b]=a)},_modeChange:function(b,a,e){var c=this.dom[a],f=this.s.position,g=d.contains(this.dom["footer"===a?"tfoot":"thead"][0],h.activeElement)?h.activeElement: 12 | null;if("in-place"===b){if(c.placeholder&&(c.placeholder.remove(),c.placeholder=null),this._unsize(a),"header"===a?c.host.prepend(this.dom.thead):c.host.append(this.dom.tfoot),c.floating)c.floating.remove(),c.floating=null}else"in"===b?(this._clone(a,e),c.floating.addClass("fixedHeader-floating").css("header"===a?"top":"bottom",this.c[a+"Offset"]).css("left",f.left+"px").css("width",f.width+"px"),"footer"===a&&c.floating.css("top","")):"below"===b?(this._clone(a,e),c.floating.addClass("fixedHeader-locked").css("top", 13 | f.tfootTop-f.theadHeight).css("left",f.left+"px").css("width",f.width+"px")):"above"===b&&(this._clone(a,e),c.floating.addClass("fixedHeader-locked").css("top",f.tbodyTop).css("left",f.left+"px").css("width",f.width+"px"));g&&g!==h.activeElement&&g.focus();this.s.scrollLeft.header=-1;this.s.scrollLeft.footer=-1;this.s[a+"Mode"]=b},_positions:function(){var b=this.s.dt.table(),a=this.s.position,e=this.dom,b=d(b.node()),c=b.children("thead"),f=b.children("tfoot"),e=e.tbody;a.visible=b.is(":visible"); 14 | a.width=b.outerWidth();a.left=b.offset().left;a.theadTop=c.offset().top;a.tbodyTop=e.offset().top;a.theadHeight=a.tbodyTop-a.theadTop;f.length?(a.tfootTop=f.offset().top,a.tfootBottom=a.tfootTop+f.outerHeight(),a.tfootHeight=a.tfootBottom-a.tfootTop):(a.tfootTop=a.tbodyTop+e.outerHeight(),a.tfootBottom=a.tfootTop,a.tfootHeight=a.tfootTop)},_scroll:function(b){var a=d(h).scrollTop(),e=d(h).scrollLeft(),c=this.s.position,f;if(this.s.enable&&(this.c.header&&(f=!c.visible||a<=c.theadTop-this.c.headerOffset? 15 | "in-place":a<=c.tfootTop-c.theadHeight-this.c.headerOffset?"in":"below",(b||f!==this.s.headerMode)&&this._modeChange(f,"header",b),this._horizontal("header",e)),this.c.footer&&this.dom.tfoot.length))a=!c.visible||a+c.windowHeight>=c.tfootBottom+this.c.footerOffset?"in-place":c.windowHeight+a>c.tbodyTop+c.tfootHeight+this.c.footerOffset?"in":"above",(b||a!==this.s.footerMode)&&this._modeChange(a,"footer",b),this._horizontal("footer",e)}});i.version="3.1.2";i.defaults={header:!0,footer:!1,headerOffset:0, 16 | footerOffset:0};d.fn.dataTable.FixedHeader=i;d.fn.DataTable.FixedHeader=i;d(h).on("init.dt.dtfh",function(b,a){if("dt"===b.namespace){var e=a.oInit.fixedHeader,c=j.defaults.fixedHeader;if((e||c)&&!a._fixedHeader)c=d.extend({},c,e),!1!==e&&new i(a,c)}});j.Api.register("fixedHeader()",function(){});j.Api.register("fixedHeader.adjust()",function(){return this.iterator("table",function(b){(b=b._fixedHeader)&&b.update()})});j.Api.register("fixedHeader.enable()",function(b){return this.iterator("table", 17 | function(a){(a=a._fixedHeader)&&a.enable(b!==k?b:!0)})});j.Api.register("fixedHeader.disable()",function(){return this.iterator("table",function(b){(b=b._fixedHeader)&&b.enable(!1)})});d.each(["header","footer"],function(b,a){j.Api.register("fixedHeader."+a+"Offset()",function(b){var c=this.context;return b===k?c.length&&c[0]._fixedHeader?c[0]._fixedHeader[a+"Offset"]():k:this.iterator("table",function(c){if(c=c._fixedHeader)c[a+"Offset"](b)})})});return i}); 18 | -------------------------------------------------------------------------------- /app/static/resource/js/dataTables.keyTable.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | KeyTable 2.1.2 3 | ©2009-2016 SpryMedia Ltd - datatables.net/license 4 | */ 5 | (function(e){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(k){return e(k,window,document)}):"object"===typeof exports?module.exports=function(k,g){k||(k=window);if(!g||!g.fn.dataTable)g=require("datatables.net")(k,g).$;return e(g,k,k.document)}:e(jQuery,window,document)})(function(e,k,g,n){var h=e.fn.dataTable,l=function(a,b){if(!h.versionCheck||!h.versionCheck("1.10.8"))throw"KeyTable requires DataTables 1.10.8 or newer";this.c=e.extend(!0,{},h.defaults.keyTable, 6 | l.defaults,b);this.s={dt:new h.Api(a),enable:!0,focusDraw:!1};this.dom={};var d=this.s.dt.settings()[0],c=d.keytable;if(c)return c;d.keytable=this;this._constructor()};e.extend(l.prototype,{blur:function(){this._blur()},enable:function(a){this.s.enable=a},focus:function(a,b){this._focus(this.s.dt.cell(a,b))},focused:function(a){if(!this.s.lastFocus)return!1;var b=this.s.lastFocus.index();return a.row===b.row&&a.column===b.column},_constructor:function(){this._tabInput();var a=this,b=this.s.dt,d=e(b.table().node()); 7 | "static"===d.css("position")&&d.css("position","relative");e(b.table().body()).on("click.keyTable","th, td",function(){if(!1!==a.s.enable){var c=b.cell(this);c.any()&&a._focus(c,null,!1)}});e(g).on("keydown.keyTable",function(b){a._key(b)});if(this.c.blurable)e(g).on("click.keyTable",function(c){e(c.target).parents(".dataTables_filter").length&&a._blur();e(c.target).parents().filter(b.table().container()).length||e(c.target).parents("div.DTE").length||a._blur()});if(this.c.editor)b.on("key.keyTable", 8 | function(b,c,d,e,g){a._editor(d,g)});if(b.settings()[0].oFeatures.bStateSave)b.on("stateSaveParams.keyTable",function(b,c,d){d.keyTable=a.s.lastFocus?a.s.lastFocus.index():null});b.on("xhr.keyTable",function(){if(!a.s.focusDraw){var c=a.s.lastFocus;c&&(a.s.lastFocus=null,b.one("draw",function(){a._focus(c)}))}});b.on("destroy.keyTable",function(){b.off(".keyTable");e(b.table().body()).off("click.keyTable","th, td");e(g.body).off("keydown.keyTable").off("click.keyTable")});var c=b.state.loaded();if(c&& 9 | c.keyTable)b.one("init",function(){var a=b.cell(c.keyTable);a.any()&&a.focus()});else this.c.focus&&b.cell(this.c.focus).focus()},_blur:function(){if(this.s.enable&&this.s.lastFocus){var a=this.s.lastFocus;e(a.node()).removeClass(this.c.className);this.s.lastFocus=null;this._emitEvent("key-blur",[this.s.dt,a])}},_columns:function(){var a=this.s.dt,b=a.columns(this.c.columns).indexes(),d=[];a.columns(":visible").every(function(a){-1!==b.indexOf(a)&&d.push(a)});return d},_editor:function(a,b){var d= 10 | this.s.dt,c=this.c.editor;b.stopPropagation();13===a&&b.preventDefault();c.inline(this.s.lastFocus.index());var f=e("div.DTE input, div.DTE textarea");f.length&&f[0].select();d.keys.enable("navigation-only");d.one("key-blur.editor",function(){c.displayed()&&c.submit()});c.one("close",function(){d.keys.enable(!0);d.off("key-blur.editor")})},_emitEvent:function(a,b){this.s.dt.iterator("table",function(d){e(d.nTable).triggerHandler(a,b)})},_focus:function(a,b,d){var c=this,f=this.s.dt,i=f.page.info(), 11 | m=this.s.lastFocus;if(this.s.enable){if("number"!==typeof a){var j=a.index(),b=j.column,a=f.rows({filter:"applied",order:"applied"}).indexes().indexOf(j.row);i.serverSide&&(a+=i.start)}if(-1!==i.length&&(a=i.start+i.length))this.s.focusDraw=!0,f.one("draw",function(){c.s.focusDraw=!1;c._focus(a,b)}).page(Math.floor(a/i.length)).draw(!1);else if(-1!==e.inArray(b,this._columns())){i.serverSide&&(a-=i.start);i=f.cell(":eq("+a+")",b,{search:"applied"});if(m){if(m.node()===i.node())return; 12 | this._blur()}m=e(i.node());m.addClass(this.c.className);if(d===n||!0===d)this._scroll(e(k),e(g.body),m,"offset"),d=f.table().body().parentNode,d!==f.table().header().parentNode&&(d=e(d.parentNode),this._scroll(d,d,m,"position"));this.s.lastFocus=i;this._emitEvent("key-focus",[this.s.dt,i]);f.state.save()}}},_key:function(a){if(this.s.enable&&!(0===a.keyCode||a.ctrlKey||a.metaKey||a.altKey)){var b=this.s.lastFocus;if(b){var d=this,c=this.s.dt;if(!(this.c.keys&&-1===e.inArray(a.keyCode,this.c.keys)))switch(a.keyCode){case 9:this._shift(a, 13 | a.shiftKey?"left":"right",!0);break;case 27:this.s.blurable&&!0===this.s.enable&&this._blur();break;case 33:case 34:a.preventDefault();var f=c.cells({page:"current"}).nodes().indexOf(b.node());c.one("draw",function(){var a=c.cells({page:"current"}).nodes();d._focus(c.cell(fe+j&&fg+a&&d=j.length-1?(h++,f=j[0]):f=j[c+1]:"left"===b?0===c?(h--,f=j[j.length-1]):f=j[c-1]:"up"===b?h--:"down"===b&&h++;0<=h&&h').css({position:"absolute",height:1,width:0,overflow:"hidden"}).insertBefore(b.table().node()).children().on("focus",function(){a._focus(b.cell(":eq(0)","0:visible",{page:"current"}))})}});l.defaults={blurable:!0,className:"focus",columns:"",editor:null,focus:null,keys:null,tabIndex:null};l.version="2.1.2";e.fn.dataTable.KeyTable= 17 | l;e.fn.DataTable.KeyTable=l;h.Api.register("cell.blur()",function(){return this.iterator("table",function(a){a.keytable&&a.keytable.blur()})});h.Api.register("cell().focus()",function(){return this.iterator("cell",function(a,b,d){a.keytable&&a.keytable.focus(b,d)})});h.Api.register("keys.disable()",function(){return this.iterator("table",function(a){a.keytable&&a.keytable.enable(!1)})});h.Api.register("keys.enable()",function(a){return this.iterator("table",function(b){b.keytable&&b.keytable.enable(a=== 18 | n?!0:a)})});h.ext.selector.cell.push(function(a,b,d){var b=b.focused,a=a.keytable,c=[];if(!a||b===n)return d;for(var e=0,g=d.length;e 8 | b.width?-1:0});this._classLogic();this._resizeAuto();d=this.c.details;!1!==d.type&&(a._detailsInit(),b.on("column-visibility.dtr",function(){a._classLogic();a._resizeAuto();a._resize()}),b.on("draw.dtr",function(){a._redrawChildren()}),c(b.table().node()).addClass("dtr-"+d.type));b.on("column-reorder.dtr",function(){a._classLogic();a._resizeAuto();a._resize()});b.on("column-sizing.dtr",function(){a._resizeAuto();a._resize()});b.on("init.dtr",function(){a._resizeAuto();a._resize();c.inArray(false, 9 | a.s.current)&&b.columns.adjust()});this._resize()},_columnsVisiblity:function(a){var b=this.s.dt,d=this.s.columns,e,f,g=d.map(function(a,b){return{columnIdx:b,priority:a.priority}}).sort(function(a,b){return a.priority!==b.priority?a.priority-b.priority:a.columnIdx-b.columnIdx}),h=c.map(d,function(b){return b.auto&&null===b.minWidth?!1:!0===b.auto?"-":-1!==c.inArray(a,b.includeIn)}),n=0;e=0;for(f=h.length;eb-d[i].minWidth?(n=!0,h[i]=!1):h[i]=!0,b-=d[i].minWidth)}g=!1;e=0;for(f=d.length;e=g&&f(c,b[d].name)}else{if("not-"===i){d=0;for(i=b.length;d").append(h).appendTo(f)}c("
").append(g).appendTo(e);"inline"===this.c.details.type&& 19 | c(d).addClass("dtr-inline collapsed");c(d).find("[name]").removeAttr("name");d=c("
").css({width:1,height:1,overflow:"hidden"}).append(d);d.insertBefore(a.table().node());g.each(function(c){c=a.column.index("fromVisible",c);b[c].minWidth=this.offsetWidth||0});d.remove()}},_setColumnVis:function(a,b){var d=this.s.dt,e=b?"":"none";c(d.column(a).header()).css("display",e);c(d.column(a).footer()).css("display",e);d.column(a).nodes().to$().css("display",e)},_tabIndexes:function(){var a=this.s.dt, 20 | b=a.cells({page:"current"}).nodes().to$(),d=a.settings()[0],e=this.c.details.target;b.filter("[data-dtr-keyboard]").removeData("[data-dtr-keyboard]");c("number"===typeof e?":eq("+e+")":e,a.rows({page:"current"}).nodes()).attr("tabIndex",d.iTabIndex).data("dtr-keyboard",1)}});j.breakpoints=[{name:"desktop",width:Infinity},{name:"tablet-l",width:1024},{name:"tablet-p",width:768},{name:"mobile-l",width:480},{name:"mobile-p",width:320}];j.display={childRow:function(a,b,d){if(b){if(c(a.node()).hasClass("parent"))return a.child(d(), 21 | "child").show(),!0}else{if(a.child.isShown())return a.child(!1),c(a.node()).removeClass("parent"),!1;a.child(d(),"child").show();c(a.node()).addClass("parent");return!0}},childRowImmediate:function(a,b,d){if(!b&&a.child.isShown()||!a.responsive.hasHidden())return a.child(!1),c(a.node()).removeClass("parent"),!1;a.child(d(),"child").show();c(a.node()).addClass("parent");return!0},modal:function(a){return function(b,d,e){if(d)c("div.dtr-modal-content").empty().append(e());else{var f=function(){g.remove(); 22 | c(k).off("keypress.dtr")},g=c('
').append(c('
').append(c('
').append(e())).append(c('
×
').click(function(){f()}))).append(c('
').click(function(){f()})).appendTo("body");c(k).on("keyup.dtr",function(a){27===a.keyCode&&(a.stopPropagation(),f())})}a&&a.header&&c("div.dtr-modal-content").prepend("

"+a.header(b)+"

")}}};j.renderer={listHidden:function(){return function(a, 23 | b,d){return(a=c.map(d,function(a){return a.hidden?'
  • '+a.title+' '+a.data+"
  • ":""}).join(""))?c('
      ').append(a):!1}},tableAll:function(a){a=c.extend({tableClass:""},a);return function(b,d,e){b=c.map(e,function(a){return'
    "}).join("");return c('
    '+a.title+": "+ 24 | a.data+"
    ').append(b)}}};j.defaults={breakpoints:j.breakpoints,auto:!0,details:{display:j.display.childRow,renderer:j.renderer.listHidden(),target:0,type:"inline"},orthogonal:"display"};var o=c.fn.dataTable.Api;o.register("responsive()",function(){return this});o.register("responsive.index()",function(a){a=c(a);return{column:a.data("dtr-index"),row:a.parent().data("dtr-index")}});o.register("responsive.rebuild()",function(){return this.iterator("table", 25 | function(a){a._responsive&&a._responsive._classLogic()})});o.register("responsive.recalc()",function(){return this.iterator("table",function(a){a._responsive&&(a._responsive._resizeAuto(),a._responsive._resize())})});o.register("responsive.hasHidden()",function(){var a=this.context[0];return a._responsive?-1!==c.inArray(!1,a._responsive.s.current):!1});j.version="2.1.0";c.fn.dataTable.Responsive=j;c.fn.DataTable.Responsive=j;c(k).on("preInit.dt.dtr",function(a,b){if("dt"===a.namespace&&(c(b.nTable).hasClass("responsive")|| 26 | c(b.nTable).hasClass("dt-responsive")||b.oInit.responsive||m.defaults.responsive)){var d=b.oInit.responsive;!1!==d&&new j(b,c.isPlainObject(d)?d:{})}});return j}); 27 | -------------------------------------------------------------------------------- /app/static/resource/js/dataTables.scroller.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Scroller 1.4.2 3 | ©2011-2016 SpryMedia Ltd - datatables.net/license 4 | */ 5 | (function(e){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(h){return e(h,window,document)}):"object"===typeof exports?module.exports=function(h,j){h||(h=window);if(!j||!j.fn.dataTable)j=require("datatables.net")(h,j).$;return e(j,h,h.document)}:e(jQuery,window,document)})(function(e,h,j,l){var m=e.fn.dataTable,g=function(a,b){this instanceof g?(b===l&&(b={}),this.s={dt:e.fn.dataTable.Api(a).settings()[0],tableTop:0,tableBottom:0,redrawTop:0,redrawBottom:0,autoHeight:!0, 6 | viewportRows:0,stateTO:null,drawTO:null,heights:{jump:null,page:null,virtual:null,scroll:null,row:null,viewport:null},topRowFloat:0,scrollDrawDiff:null,loaderVisible:!1},this.s=e.extend(this.s,g.oDefaults,b),this.s.heights.row=this.s.rowHeight,this.dom={force:j.createElement("div"),scroller:null,table:null,loader:null},this.s.dt.oScroller||(this.s.dt.oScroller=this,this._fnConstruct())):alert("Scroller warning: Scroller must be initialised with the 'new' keyword.")};e.extend(g.prototype,{fnRowToPixels:function(a, 7 | b,c){a=c?this._domain("virtualToPhysical",a*this.s.heights.row):this.s.baseScrollTop+(a-this.s.baseRowTop)*this.s.heights.row;return b||b===l?parseInt(a,10):a},fnPixelsToRow:function(a,b,c){var d=a-this.s.baseScrollTop,a=c?this._domain("physicalToVirtual",a)/this.s.heights.row:d/this.s.heights.row+this.s.baseRowTop;return b||b===l?parseInt(a,10):a},fnScrollToRow:function(a,b){var c=this,d=!1,f=this.fnRowToPixels(a),i=a-(this.s.displayBuffer-1)/2*this.s.viewportRows;0>i&&(i=0);if((f>this.s.redrawBottom|| 8 | ftable",this.dom.scroller)[0];this.dom.table.style.position="absolute";this.dom.table.style.top="0px";this.dom.table.style.left="0px";e(this.s.dt.nTableWrapper).addClass("DTS");this.s.loadingIndicator&&(this.dom.loader=e('
    '+this.s.dt.oLanguage.sLoadingRecords+"
    ").css("display", 11 | "none"),e(this.dom.scroller.parentNode).css("position","relative").append(this.dom.loader));this.s.heights.row&&"auto"!=this.s.heights.row&&(this.s.autoHeight=!1);this.fnMeasure(!1);this.s.ingnoreScroll=!0;this.s.stateSaveThrottle=this.s.dt.oApi._fnThrottle(function(){a.s.dt.oApi._fnSaveState(a.s.dt)},500);e(this.dom.scroller).on("scroll.DTS",function(){a._fnScroll.call(a)});e(this.dom.scroller).on("touchstart.DTS",function(){a._fnScroll.call(a)});this.s.dt.aoDrawCallback.push({fn:function(){a.s.dt.bInitialised&& 12 | a._fnDrawCallback.call(a)},sName:"Scroller"});e(h).on("resize.DTS",function(){a.fnMeasure(false);a._fnInfo()});var b=!0;this.s.dt.oApi._fnCallbackReg(this.s.dt,"aoStateSaveParams",function(c,d){if(b&&a.s.dt.oLoadedState){d.iScroller=a.s.dt.oLoadedState.iScroller;d.iScrollerTopRow=a.s.dt.oLoadedState.iScrollerTopRow;b=false}else{d.iScroller=a.dom.scroller.scrollTop;d.iScrollerTopRow=a.s.topRowFloat}},"Scroller_State");this.s.dt.oLoadedState&&(this.s.topRowFloat=this.s.dt.oLoadedState.iScrollerTopRow|| 13 | 0);e(this.s.dt.nTable).one("init.dt",function(){a.fnMeasure()});this.s.dt.aoDestroyCallback.push({sName:"Scroller",fn:function(){e(h).off("resize.DTS");e(a.dom.scroller).off("touchstart.DTS scroll.DTS");e(a.s.dt.nTableWrapper).removeClass("DTS");e("div.DTS_Loading",a.dom.scroller.parentNode).remove();e(a.s.dt.nTable).off("init.dt");a.dom.table.style.position="";a.dom.table.style.top="";a.dom.table.style.left=""}})}else this.s.dt.oApi._fnLog(this.s.dt,0,"Pagination must be enabled for Scroller")}, 14 | _fnScroll:function(){var a=this,b=this.s.heights,c=this.dom.scroller.scrollTop,d;if(!this.s.skip&&!this.s.ingnoreScroll)if(this.s.dt.bFiltered||this.s.dt.bSorted)this.s.lastScrollTop=0;else{this._fnInfo();clearTimeout(this.s.stateTO);this.s.stateTO=setTimeout(function(){a.s.dt.oApi._fnSaveState(a.s.dt)},250);if(cthis.s.redrawBottom){var f=Math.ceil((this.s.displayBuffer-1)/2*this.s.viewportRows);Math.abs(c-this.s.lastScrollTop)>b.viewport||this.s.ani?(d=parseInt(this._domain("physicalToVirtual", 15 | c)/b.row,10)-f,this.s.topRowFloat=this._domain("physicalToVirtual",c)/b.row):(d=this.fnPixelsToRow(c)-f,this.s.topRowFloat=this.fnPixelsToRow(c,!1));0>=d?d=0:d+this.s.dt._iDisplayLength>this.s.dt.fnRecordsDisplay()?(d=this.s.dt.fnRecordsDisplay()-this.s.dt._iDisplayLength,0>d&&(d=0)):0!==d%2&&d++;if(d!=this.s.dt._iDisplayStart&&(this.s.tableTop=e(this.s.dt.nTable).offset().top,this.s.tableBottom=e(this.s.dt.nTable).height()+this.s.tableTop,b=function(){if(a.s.scrollDrawReq===null)a.s.scrollDrawReq= 16 | c;a.s.dt._iDisplayStart=d;a.s.dt.oApi._fnDraw(a.s.dt)},this.s.dt.oFeatures.bServerSide?(clearTimeout(this.s.drawTO),this.s.drawTO=setTimeout(b,this.s.serverWait)):b(),this.dom.loader&&!this.s.loaderVisible))this.dom.loader.css("display","block"),this.s.loaderVisible=!0}else this.s.topRowFloat=this._domain("physicalToVirtual",c)/b.row;this.s.lastScrollTop=c;this.s.stateSaveThrottle()}},_domain:function(a,b){var c=this.s.heights,d;if(c.virtual===c.scroll)return b;var e=(c.scroll-c.viewport)/2,i=(c.virtual- 17 | c.viewport)/2;d=i/(e*e);if("virtualToPhysical"===a){if(bb?c.scroll:2*e-Math.pow(b/d,0.5)}if("physicalToVirtual"===a){if(bb?c.virtual:2*i-b*b*d}},_fnDrawCallback:function(){var a=this,b=this.s.heights,c=this.dom.scroller.scrollTop,d=e(this.s.dt.nTable).height(),f=this.s.dt._iDisplayStart,i=this.s.dt._iDisplayLength,g=this.s.dt.fnRecordsDisplay();this.s.skip=!0;this._fnScrollForce();c=0===f?this.s.topRowFloat*b.row:f+i>=g? 18 | b.scroll-(g-this.s.topRowFloat)*b.row:this._domain("virtualToPhysical",this.s.topRowFloat*b.row);this.dom.scroller.scrollTop=c;this.s.baseScrollTop=c;this.s.baseRowTop=this.s.topRowFloat;var h=c-(this.s.topRowFloat-f)*b.row;0===f?h=0:f+i>=g&&(h=b.scroll-d);this.dom.table.style.top=h+"px";this.s.tableTop=h;this.s.tableBottom=d+this.s.tableTop;d=(c-this.s.tableTop)*this.s.boundaryScale;this.s.redrawTop=c-d;this.s.redrawBottom=c+d;this.s.skip=!1;this.s.dt.oFeatures.bStateSave&&null!==this.s.dt.oLoadedState&& 19 | "undefined"!=typeof this.s.dt.oLoadedState.iScroller?((c=(this.s.dt.sAjaxSource||a.s.dt.ajax)&&!this.s.dt.oFeatures.bServerSide?!0:!1)&&2==this.s.dt.iDraw||!c&&1==this.s.dt.iDraw)&&setTimeout(function(){e(a.dom.scroller).scrollTop(a.s.dt.oLoadedState.iScroller);a.s.redrawTop=a.s.dt.oLoadedState.iScroller-b.viewport/2;setTimeout(function(){a.s.ingnoreScroll=!1},0)},0):a.s.ingnoreScroll=!1;this.s.dt.oFeatures.bInfo&&setTimeout(function(){a._fnInfo.call(a)},0);this.dom.loader&&this.s.loaderVisible&& 20 | (this.dom.loader.css("display","none"),this.s.loaderVisible=!1)},_fnScrollForce:function(){var a=this.s.heights;a.virtual=a.row*this.s.dt.fnRecordsDisplay();a.scroll=a.virtual;1E6this.s.heights.row?a.scroll+"px":this.s.heights.row+"px"},_fnCalcRowHeight:function(){var a=this.s.dt,b=a.nTable,c=b.cloneNode(!1),d=e("
    ").appendTo(c),f=e('
    ');for(e("tbody tr:lt(4)",b).clone().appendTo(d);3>e("tr",d).length;)d.append("
    ");e("div."+a.oClasses.sScrollBody,f).append(c);a=this.s.dt.nHolding||b.parentNode;e(a).is(":visible")||(a="body");f.appendTo(a);this.s.heights.row=e("tr",d).eq(1).outerHeight();f.remove()},_fnInfo:function(){if(this.s.dt.oFeatures.bInfo){var a=this.s.dt,b=a.oLanguage,c=this.dom.scroller.scrollTop,d=Math.floor(this.fnPixelsToRow(c,!1,this.s.ani)+1),f=a.fnRecordsTotal(), 22 | i=a.fnRecordsDisplay(),c=Math.ceil(this.fnPixelsToRow(c+this.s.heights.viewport,!1,this.s.ani)),c=ip&&(p=-50);g(this);return c.each(function(){var a=f(this);E(a);var c=this, 8 | b=c.id,g=-p+"%",d=100+2*p+"%",d={position:"absolute",top:g,left:g,display:"block",width:d,height:d,margin:0,padding:0,background:"#fff",border:0,opacity:0},g=_mobile?{position:"absolute",visibility:"hidden"}:p?d:{position:"absolute",opacity:0},l="checkbox"==c[_type]?e.checkboxClass||"icheckbox":e.radioClass||"i"+r,z=f(_label+'[for="'+b+'"]').add(a.closest(_label)),u=!!e.aria,y=m+"-"+Math.random().toString(36).substr(2,6),h='
    ")[_callback]("ifCreated").parent().append(e.insert);d=f('').css(d).appendTo(h);a.data(m,{o:e,s:a.attr("style")}).css(g);e.inheritClass&&h[_add](c.className||"");e.inheritID&&b&&h.attr("id",m+"-"+b);"static"==h.css("position")&&h.css("position","relative");A(a,!0,_update);if(z.length)z.on(_click+".i mouseover.i mouseout.i "+_touch,function(b){var d=b[_type],e=f(this);if(!c[n]){if(d==_click){if(f(b.target).is("a"))return; 10 | A(a,!1,!0)}else B&&(/ut|nd/.test(d)?(h[_remove](v),e[_remove](w)):(h[_add](v),e[_add](w)));if(_mobile)b.stopPropagation();else return!1}});a.on(_click+".i focus.i blur.i keyup.i keydown.i keypress.i",function(b){var d=b[_type];b=b.keyCode;if(d==_click)return!1;if("keydown"==d&&32==b)return c[_type]==r&&c[k]||(c[k]?q(a,k):x(a,k)),!1;if("keyup"==d&&c[_type]==r)!c[k]&&x(a,k);else if(/us|ur/.test(d))h["blur"==d?_remove:_add](s)});d.on(_click+" mousedown mouseup mouseover mouseout "+_touch,function(b){var d= 11 | b[_type],e=/wn|up/.test(d)?t:v;if(!c[n]){if(d==_click)A(a,!1,!0);else{if(/wn|er|in/.test(d))h[_add](e);else h[_remove](e+" "+t);if(z.length&&B&&e==v)z[/ut|nd/.test(d)?_remove:_add](w)}if(_mobile)b.stopPropagation();else return!1}})})}})(window.jQuery||window.Zepto); 12 | -------------------------------------------------------------------------------- /app/static/resource/js/macarons.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module. 4 | define(['exports', 'echarts'], factory); 5 | } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') { 6 | // CommonJS 7 | factory(exports, require('echarts')); 8 | } else { 9 | // Browser globals 10 | factory({}, root.echarts); 11 | } 12 | }(this, function (exports, echarts) { 13 | var log = function (msg) { 14 | if (typeof console !== 'undefined') { 15 | console && console.error && console.error(msg); 16 | } 17 | }; 18 | if (!echarts) { 19 | log('ECharts is not Loaded'); 20 | return; 21 | } 22 | 23 | var colorPalette = [ 24 | '#2ec7c9','#b6a2de','#5ab1ef','#ffb980','#d87a80', 25 | '#8d98b3','#e5cf0d','#97b552','#95706d','#dc69aa', 26 | '#07a2a4','#9a7fd1','#588dd5','#f5994e','#c05050', 27 | '#59678c','#c9ab00','#7eb00a','#6f5553','#c14089' 28 | ]; 29 | 30 | 31 | var theme = { 32 | color: colorPalette, 33 | 34 | title: { 35 | textStyle: { 36 | fontWeight: 'normal', 37 | color: '#008acd' 38 | } 39 | }, 40 | 41 | visualMap: { 42 | itemWidth: 15, 43 | color: ['#5ab1ef','#e0ffff'] 44 | }, 45 | 46 | toolbox: { 47 | iconStyle: { 48 | normal: { 49 | borderColor: colorPalette[0] 50 | } 51 | } 52 | }, 53 | 54 | tooltip: { 55 | backgroundColor: 'rgba(50,50,50,0.5)', 56 | axisPointer : { 57 | type : 'line', 58 | lineStyle : { 59 | color: '#008acd' 60 | }, 61 | crossStyle: { 62 | color: '#008acd' 63 | }, 64 | shadowStyle : { 65 | color: 'rgba(200,200,200,0.2)' 66 | } 67 | } 68 | }, 69 | 70 | dataZoom: { 71 | dataBackgroundColor: '#efefff', 72 | fillerColor: 'rgba(182,162,222,0.2)', 73 | handleColor: '#008acd' 74 | }, 75 | 76 | grid: { 77 | borderColor: '#eee' 78 | }, 79 | 80 | categoryAxis: { 81 | axisLine: { 82 | lineStyle: { 83 | color: '#008acd' 84 | } 85 | }, 86 | splitLine: { 87 | lineStyle: { 88 | color: ['#eee'] 89 | } 90 | } 91 | }, 92 | 93 | valueAxis: { 94 | axisLine: { 95 | lineStyle: { 96 | color: '#008acd' 97 | } 98 | }, 99 | splitArea : { 100 | show : true, 101 | areaStyle : { 102 | color: ['rgba(250,250,250,0.1)','rgba(200,200,200,0.1)'] 103 | } 104 | }, 105 | splitLine: { 106 | lineStyle: { 107 | color: ['#eee'] 108 | } 109 | } 110 | }, 111 | 112 | timeline : { 113 | lineStyle : { 114 | color : '#008acd' 115 | }, 116 | controlStyle : { 117 | normal : { color : '#008acd'}, 118 | emphasis : { color : '#008acd'} 119 | }, 120 | symbol : 'emptyCircle', 121 | symbolSize : 3 122 | }, 123 | 124 | line: { 125 | smooth : true, 126 | symbol: 'emptyCircle', 127 | symbolSize: 3 128 | }, 129 | 130 | candlestick: { 131 | itemStyle: { 132 | normal: { 133 | color: '#d87a80', 134 | color0: '#2ec7c9', 135 | lineStyle: { 136 | color: '#d87a80', 137 | color0: '#2ec7c9' 138 | } 139 | } 140 | } 141 | }, 142 | 143 | scatter: { 144 | symbol: 'circle', 145 | symbolSize: 4 146 | }, 147 | 148 | map: { 149 | label: { 150 | normal: { 151 | textStyle: { 152 | color: '#d87a80' 153 | } 154 | } 155 | }, 156 | itemStyle: { 157 | normal: { 158 | borderColor: '#eee', 159 | areaColor: '#ddd' 160 | }, 161 | emphasis: { 162 | areaColor: '#fe994e' 163 | } 164 | } 165 | }, 166 | 167 | graph: { 168 | color: colorPalette 169 | }, 170 | 171 | gauge : { 172 | axisLine: { 173 | lineStyle: { 174 | color: [[0.2, '#2ec7c9'],[0.8, '#5ab1ef'],[1, '#d87a80']], 175 | width: 10 176 | } 177 | }, 178 | axisTick: { 179 | splitNumber: 10, 180 | length :15, 181 | lineStyle: { 182 | color: 'auto' 183 | } 184 | }, 185 | splitLine: { 186 | length :22, 187 | lineStyle: { 188 | color: 'auto' 189 | } 190 | }, 191 | pointer : { 192 | width : 5 193 | } 194 | } 195 | }; 196 | 197 | echarts.registerTheme('macarons', theme); 198 | })); -------------------------------------------------------------------------------- /app/static/resource/js/responsive.bootstrap.js: -------------------------------------------------------------------------------- 1 | /*! Bootstrap integration for DataTables' Responsive 2 | * ©2015-2016 SpryMedia Ltd - datatables.net/license 3 | */ 4 | 5 | (function( factory ){ 6 | if ( typeof define === 'function' && define.amd ) { 7 | // AMD 8 | define( ['jquery', 'datatables.net-bs', 'datatables.net-responsive'], function ( $ ) { 9 | return factory( $, window, document ); 10 | } ); 11 | } 12 | else if ( typeof exports === 'object' ) { 13 | // CommonJS 14 | module.exports = function (root, $) { 15 | if ( ! root ) { 16 | root = window; 17 | } 18 | 19 | if ( ! $ || ! $.fn.dataTable ) { 20 | $ = require('datatables.net-bs')(root, $).$; 21 | } 22 | 23 | if ( ! $.fn.dataTable.Responsive ) { 24 | require('datatables.net-responsive')(root, $); 25 | } 26 | 27 | return factory( $, root, root.document ); 28 | }; 29 | } 30 | else { 31 | // Browser 32 | factory( jQuery, window, document ); 33 | } 34 | }(function( $, window, document, undefined ) { 35 | 'use strict'; 36 | var DataTable = $.fn.dataTable; 37 | 38 | 39 | var _display = DataTable.Responsive.display; 40 | var _original = _display.modal; 41 | var _modal = $( 42 | '
     
    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {% for work in works %} 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 54 | 55 | {% endfor %} 56 | 57 | 58 |
    工单名数据库备份开发人创建时间完成时间状态操作
    {{ work.name }}{{ work.db_config }}{% if work.backup == true %}是{% else %}否{% endif %}{{ work.dev }}{{ work.create_time }}{% if work.finish_time %}{{ work.finish_time }}{% else %}未确定{% endif %}{% if work.status == 0 %}结束{% elif work.status == 1%}待人工审核{% elif work.status == 2 %}自动审核失败{% elif work.status == 3 %}执行中{% elif work.status == 4 %}执行异常{% elif work.status == 5 %}开发人中止{% elif work.status == 6 %}审核人中止{% elif work.status == 7 %}管理员中止{% elif work.status == 8 %}审核人驳回{% endif %}查看
    59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/audit_work_assign.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 |
    7 |
    8 |
    9 |

    分派审核人

    10 |
    11 |
    12 |
    13 |
    14 |
    15 | {{ form.hidden_tag() }} 16 |
    17 | 18 |
    19 | 24 |
    25 |
    26 | 27 | 28 |
    29 |
    30 |
    31 | 32 | 33 |
    34 |
    35 | 36 |
    37 |
    38 |
    39 |
    40 |
    41 |
    42 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/audit_work_pending.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 | 7 |
    8 |
    9 |
    10 |

    待审核工单

    11 | 12 |
    13 |
    14 |
    15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {% for work in works %} 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 54 | 55 | {% endfor %} 56 | 57 | 58 |
    工单名数据库备份开发人创建时间完成时间状态操作
    {{ work.name }}{{ work.db_config }}{% if work.backup == true %}是{% else %}否{% endif %}{{ work.dev }}{{ work.create_time }}{% if work.finish_time %}{{ work.finish_time }}{% else %}未确定{% endif %}{% if work.status == 0 %}结束{% elif work.status == 1%}待人工审核{% elif work.status == 2 %}自动审核失败{% elif work.status == 3 %}执行中{% elif work.status == 4 %}执行异常{% elif work.status == 5 %}开发人中止{% elif work.status == 6 %}审核人中止{% elif work.status == 7 %}管理员中止{% endif %}查看
    59 |
    60 |
    61 |
    62 | 63 |
    64 |
    65 | 66 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/audit_work_timer.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 |
    7 |
    8 |
    9 |

    {{ work.name }}设置定时执行

    10 |
    11 |
    12 |
    13 |
    14 |
    15 | 16 |
    17 |   {% if work.timer %}定时执行时间为{{ work.timer }}{% else %}未设置定时执行{% endif %} 18 |
    19 |
    20 |
    21 |
    22 | 23 |
    24 | 25 |
    26 |
    27 |
    28 | 29 | {% if work.timer %}{% endif %} 30 | 31 |
    32 |
    33 | 34 |
    35 |
    36 |
    37 |
    38 |
    39 |
    40 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/audit_work_timer_view.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 | 7 |
    8 |
    9 |
    10 |

    定时任务

    11 |
    12 |
    13 |
    14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% for work in works %} 28 | 29 | 30 | 31 | 32 | 33 | 34 | {% endfor %} 35 | 36 | 37 |
    工单名任务执行时间操作
    {{ work.name }}{{ work.timer }}查看
    38 |
    39 |
    40 |
    41 | 42 |
    43 |
    44 | 45 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 自动化运维平台 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
    33 |
    34 |
    35 |
    36 | 39 | 40 |
    41 | 42 | 43 |
    44 |
    45 | ... 46 |
    47 |
    48 | 欢迎, 49 |

    {{ current_user.name }}{% if current_user.role == 'dev' %}(开发人){% elif current_user.role == 'audit' and current_user.srole == 0 %}(主审核){% elif current_user.role == 'audit' and current_user.srole == 1 %}(副审核){% elif current_user.role == 'admin'%}(管理员){% endif %}

    50 |
    51 |
    52 | 53 | 54 |
    55 | {% block menu %}{% endblock %} 56 | 57 | 58 | 59 |
    60 |
    61 | 62 | 63 |
    64 | 86 |
    87 | 88 |
    89 | {% with messages = get_flashed_messages() %} 90 | {% if messages %} 91 |
      92 | {% for message in messages %} 93 |
    • {{ message }}
    • 94 | {% endfor %} 95 |
    96 | {% endif %} 97 | {% endwith %} 98 |
    99 | 100 | 101 | {% block content %} 102 | {% endblock %} 103 | 104 | 105 |
    106 |
    107 | Inception_web - 自动化管理平台 by Logo. 108 |
    109 |
    110 | 111 |
    112 | 113 |
    114 |
    115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /app/templates/dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block menu %} 4 | 47 | 48 | {% endblock %} 49 | {% block content %} 50 |
    51 |
    52 |
    53 |

    使用指引

    54 |
    55 | 56 |
    57 |
    58 |

    欢迎使用inception_web!

    59 |
    60 |

    本系统是MySQL自动化管理工具,配合Inception使用,基于archer进行二次开发,进行了一些补充优化。要会使用本系统,正确的姿势是先了解MySQL规范。互联网业界有成熟的MySQL设计规范,上线前请给所有人员进行培训,熟悉其中道理。

    61 |
    62 |

    点击下载规范

    63 |
    64 |

    使用简要:

    65 |

    1.管理员登录

    66 |

    2.添加开发人员,为开发人员分配数据库

    67 |

    3.添加审核人员(主副审核根据需要)

    68 |

    4.开始工单流程

    69 |
    70 |

    有更多idea欢迎和我一起交流分享,谢谢!我的QQ:496080199

    71 |
    72 |
    73 |
    74 |
    75 | {% endblock %} 76 | 77 | -------------------------------------------------------------------------------- /app/templates/dbreport.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 | 7 |
    8 |
    9 |
    10 |
    11 |

    {{ dbconfig.name }}数据库报告

    12 | 13 | 14 | 15 | {{ form.hidden_tag() }} 16 | 19 |
    20 |
    21 |
    22 |
    23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% for dbreport in dbreports %} 37 | 38 | 39 | 40 | 41 | 42 | {% endfor %} 43 | 44 | 45 |
    创建时间内存大小操作
    {{ dbreport.create_time }}{{ dbreport.mem }} MB
    46 |
    47 | 48 |
    49 |
    50 | 51 |
    52 |
    53 | 54 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/dbreport_view.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 | 7 |
    8 |
    9 |
    10 |

    {{ dbreport.db_name }}在{{ dbreport.create_time }}生成的配置报告

    11 |
    12 |
    13 |
    14 | 15 |
    16 | 17 |
    18 |
    19 | 20 |
    21 |
    22 | 23 | 24 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/dev_chart.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 |
    7 |

    {{ days }}天内图表  1天 3天 7天 15天 30天

    8 |
    9 |
    10 |
    11 |
    12 | 13 |
    14 | 15 |
    16 | 17 |
    18 |
    19 |
    20 |
    21 |
    22 | 23 | 24 |
    25 | 26 |
    27 | 28 |
    29 |
    30 |
    31 | 32 |
    33 |
    34 | 35 | 36 | 37 | 38 | 39 | 163 | 164 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/dev_work.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 | 7 |
    8 |
    9 |
    10 |

    我的工单

    11 | 14 |
    15 |
    16 |
    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {% for work in works %} 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 49 | 50 | {% endfor %} 51 | 52 | 53 |
    工单名数据库备份审核人创建时间完成时间状态操作
    {{ work.name }}{{ work.db_config }}{% if work.backup == true %}是{% else %}否{% endif %}{{ work.audit }}{% if work.srole == 0 %}(主审核){% elif work.srole == 1%}(副审核){% endif %}{{ work.create_time }}{% if work.finish_time %}{{ work.finish_time }}{% else %}未确定{% endif %}{% if work.status == 0 %}结束{% elif work.status == 1%}待人工审核{% elif work.status == 2 %}自动审核失败{% elif work.status == 3 %}执行中{% elif work.status == 4 %}执行异常{% elif work.status == 5 %}开发人中止{% elif work.status == 6 %}审核人中止{% elif work.status == 7 %}管理员中止{% elif work.status == 8 %}审核人驳回{% endif %} 45 | 查看 46 | {% if work.status == 2 or work.status == 8 %}{% endif %} 47 | {% if work.status == 2 %}{% endif %}{% if work.status == 1 %}{% endif %} 48 |
    54 |
    55 |
    56 |
    57 | 58 |
    59 |
    60 | 61 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/dev_work_create.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 | 5 | 6 |
    7 |
    8 |
    9 | {{ form.hidden_tag() }} 10 |
    11 |
    12 |
    13 |

    新增工单

    14 |
    15 |
    16 |
    17 |
    18 | 19 |
    20 | 22 |
    23 | 24 |
    25 |
    26 |
    27 | 29 |
    30 | 36 |
    37 |
    38 |
    39 | 40 |
    41 | 45 |
    46 |
    47 |
    48 | 50 |
    51 | 57 |
    58 |
    59 | 60 | 61 |
    62 |
    63 |
    64 | 65 | 66 |
    67 |
    68 |
    69 |
    70 |
    71 | {% with messages = get_flashed_messages() %} 72 | {% if messages %} 73 |
      74 | {% for message in messages %} 75 |
    • {{ message }}
    • 76 | {% endfor %} 77 |
    78 | {% endif %} 79 | {% endwith %} 80 |
    81 |
    82 | 83 | 84 |
    85 |
    86 |
    87 |
    88 |
    89 |
    90 |
    91 |
    92 | 93 | 95 |
    96 | 97 |
    98 |
    99 |
    100 |
    101 |
    102 |
    103 |
    104 |
    105 | 106 | 107 | 161 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/dev_work_update.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 | 5 | 6 |
    7 |
    8 |
    9 | {{ form.hidden_tag() }} 10 |
    11 |
    12 |
    13 |

    新增工单

    14 |
    15 |
    16 |
    17 |
    18 | 19 |
    20 | 22 | 23 |
    24 | {{ work.name }} 25 |
    26 |
    27 |
    28 | 30 | 31 |
    32 | {{ work.db_config }} 33 |
    34 |
    35 |
    36 | 37 | 38 |
    39 | {% if work.backup == true %}是{% else %}否{% endif %} 40 |
    41 |
    42 |
    43 | 45 | 46 |
    47 | {{ work.audit }} 48 |
    49 |
    50 | 51 | 52 |
    53 |
    54 |
    55 | 56 | 57 |
    58 |
    59 |
    60 |
    61 |
    62 | {% with messages = get_flashed_messages() %} 63 | {% if messages %} 64 |
      65 | {% for message in messages %} 66 |
    • {{ message }}
    • 67 | {% endfor %} 68 |
    69 | {% endif %} 70 | {% endwith %} 71 |
    72 |
    73 | 74 | 75 |
    76 |
    77 |
    78 |
    79 |
    80 |
    81 |
    82 |
    83 | 84 | 86 |
    87 | 88 |
    89 |
    90 |
    91 |
    92 |
    93 |
    94 |
    95 |
    96 | 97 | 98 | 148 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 自动化运维平台 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 59 | 60 | 61 | 62 |
    63 | 64 | 65 | 66 | 120 |
    121 | 122 | 123 | -------------------------------------------------------------------------------- /app/templates/modules.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 |
    7 |

    模块管理 8 |

    9 |
    10 |
    11 |
    12 |
    13 |
    SQLAdvisor
    14 |
    15 |
    16 | 17 |

    SQLAdvisor是由美团点评公司技术工程部DBA团队(北京)开发维护的一个分析SQL给出索引优化建议的工具。它基于MySQL原生态词法解析,结合分析SQL中的where条件、聚合条件、多表Join关系 给出索引优化建议。

    18 |
    19 | 20 |

    {{ checksqladvisorresult }}

    21 |
    22 | {% if checksqladvisorresult == 'SQLAdvisor未安装' %} 23 | 24 | 25 | {% else %} 26 | 27 | 28 | {% endif %} 29 | 30 | 31 |
    32 |
    33 |
    34 |
    35 |
    36 | 37 | 38 |
    39 | 40 |
    41 | 42 |
    43 |
    44 |
    45 |
    46 |
    47 | 48 | 49 |
    50 | 51 |
    52 | 53 |
    54 |
    55 |
    56 |
    57 |
    58 | 59 | 60 |
    61 | 62 |
    63 | 64 |
    65 |
    66 |
    67 | 68 |
    69 |
    70 | 71 | 72 | 73 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/mysql_db.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 | 7 |
    8 |
    9 |
    10 |

    MySQL数据库

    11 | 14 |
    15 |
    16 |
    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {% for dbconfig in dbconfigs %} 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | {% endfor %} 45 | 46 | 47 |
    名称主机端口用户名创建时间更新时间操作
    {{ dbconfig.name }}{{ dbconfig.host }}{{ dbconfig.port }}{{ dbconfig.user }}{{ dbconfig.create_time }}{{ dbconfig.update_time }}  
    48 |
    49 |
    50 |
    51 | 52 |
    53 |
    54 | 55 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/mysql_db_create.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 |
    7 |
    8 |
    9 |

    新增MySQL数据库

    10 |
    11 |
    12 |
    13 |
    14 |
    15 | {{ form.hidden_tag() }} 16 |
    17 | 19 |
    20 | 21 |
    22 |
    23 |
    24 | 26 |
    27 | 28 |
    29 |
    30 |
    31 | 32 |
    33 | 34 |
    35 |
    36 |
    37 | 38 |
    39 | 40 |
    41 |
    42 |
    43 | 44 |
    45 | 46 |
    47 |
    48 | 49 | 50 |
    51 |
    52 |
    53 | 54 | 55 | 56 |
    57 |
    58 | 59 |
    60 |
    61 |
    62 |
    63 |
    64 |
    65 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/mysql_db_update.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 |
    7 |
    8 |
    9 |

    修改MySQL数据库

    10 |
    11 |
    12 |
    13 |
    14 |
    15 | {{ form.hidden_tag() }} 16 |
    17 | 19 |
    20 | 21 |
    22 |
    23 |
    24 | 26 |
    27 | 28 |
    29 |
    30 |
    31 | 32 |
    33 | 34 |
    35 |
    36 |
    37 | 38 |
    39 | 40 |
    41 |
    42 |
    43 | 44 |
    45 | 46 |
    47 |
    48 | 49 | 50 |
    51 |
    52 |
    53 | 54 | 55 | 56 |
    57 |
    58 | 59 |
    60 |
    61 |
    62 |
    63 |
    64 |
    65 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/passwd.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 |
    7 |
    8 |
    9 |

    修改密码

    10 |
    11 |
    12 |
    13 |
    14 |
    15 | {{ form.hidden_tag() }} 16 |
    17 | 18 |
    19 | 20 |
    21 |
    22 |
    23 | 24 |
    25 | 26 |
    27 |
    28 |
    29 | 30 |
    31 | 32 |
    33 |
    34 | 35 | 36 |
    37 |
    38 |
    39 | 40 | 41 |
    42 |
    43 | 44 |
    45 |
    46 |
    47 |
    48 |
    49 |
    50 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/slowlog.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 | 7 |
    8 |
    9 |
    10 |

    慢查询

    11 | 12 |
    13 |
    14 |
    15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% for dbconfig in dbconfigs %} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {% endfor %} 36 | 37 | 38 |
    数据库
    {{ dbconfig.name }}1小时3小时6小时12小时24小时
    39 |
    40 |
    41 |
    42 | 43 |
    44 |
    45 | 46 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/sqladvisor_check.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 | 5 | 6 |
    7 |
    8 |
    9 |
    10 |
    11 |

    SQLAdvisor优化(此功能需安装模块)

    12 |
    13 |
    14 |
    15 |
    16 |
    17 | 18 | 19 | 21 |
    22 | 23 | 24 |
    25 | 26 |
    27 | 29 |
    30 | 36 |
    37 | 39 |
    40 | 43 |
    44 |
    45 | 46 |
    47 | 48 |
    49 | 50 |
    51 |
    52 |
    53 |
    54 |
    55 |
    56 | 57 | 58 | 132 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/user.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 | 7 |
    8 |
    9 |
    10 |

    人员管理

    11 | 14 |
    15 |
    16 |
    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {% for user in users %} 32 | 33 | 34 | 35 | 36 | 41 | 42 | {% endfor %} 43 | 44 | 45 |
    用户名角色邮箱操作
    {{ user.name }}{% if user.role == 'dev' %}开发人员{% elif user.role == 'audit' and user.srole == 0 %}主审核人{% elif user.role == 'audit' and user.srole == 1 %}副审核人{% endif %}{{ user.email }} 37 | {% if user.role == 'audit' and user.srole == 0 %}{% elif user.role == 'audit' and user.srole == 1 %}{% endif %} 38 | {% if user.role == 'dev' %}{% endif %} 39 | 40 |
    46 |
    47 |
    48 |
    49 | 50 |
    51 |
    52 | 53 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/user_create.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 |
    7 |
    8 |
    9 |

    新增用户

    10 |
    11 |
    12 |
    13 |
    14 |
    15 | {{ form.hidden_tag() }} 16 |
    17 | 19 |
    20 | 21 |
    22 |
    23 |
    24 | 26 |
    27 | 28 |
    29 |
    30 |
    31 | 32 |
    33 | 37 |
    38 |
    39 |
    40 | 41 |
    42 | 43 |
    44 |
    45 | 46 | 47 |
    48 |
    49 |
    50 | 51 | 52 | 53 |
    54 |
    55 | 56 |
    57 |
    58 |
    59 |
    60 |
    61 |
    62 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/user_db_alloc.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 |
    7 |
    8 |
    9 |

    为{{ user.name }}分配数据库

    10 |
    11 |
    12 |
    13 |
    14 |
    15 | {{ form.hidden_tag() }} 16 | 17 | 22 |
    23 | 24 | 25 |
    26 | 27 |
    28 | 29 | 30 | 31 | {% if not userdbconfigs %}{% endif %} 32 | {% for dbconfig in userdbconfigs %} 33 | 34 | 35 | 36 | 37 | 38 | 39 | {% endfor %} 40 | 41 |
    暂无
    {{ dbconfig.name }}({{ dbconfig.host }})
    42 | 43 | 44 |
    45 |
    46 |
    47 |
    48 |
    49 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/user_update.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 |
    7 |
    8 |
    9 |

    修改用户

    10 |
    11 |
    12 |
    13 |
    14 |
    15 | {{ form.hidden_tag() }} 16 |
    17 | 19 | 20 |
    21 | {{ user.name }} 22 |
    23 |
    24 |
    25 | 27 | 28 |
    29 | {% if user.role == 'dev' %}开发人员{% endif %} 30 | {% if user.role == 'audit' %}审核人员{% endif %} 31 |
    32 |
    33 |
    34 | 36 |
    37 | 38 |
    39 |
    40 |
    41 | 42 |
    43 | 44 |
    45 |
    46 | 47 | 48 |
    49 |
    50 |
    51 | 52 | 53 |
    54 |
    55 | 56 |
    57 |
    58 |
    59 |
    60 |
    61 |
    62 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/view_slowlog.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 | 7 |
    8 |
    9 |
    10 |

    慢查询数据库:{{ dbconfig.name }}

    11 | 14 |
    15 |
    16 |
    17 |
    18 |
    19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {% for row in slowloglist %} 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% endfor %} 37 | 38 |
    ID慢查询sql计数
    {{ loop.index }}{{ row.0 }}{{ row.1 }}
    39 | 40 |
    41 | 42 |
    43 |
    44 |
    45 | 46 |
    47 |
    48 |
    49 | 50 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/work_view.html: -------------------------------------------------------------------------------- 1 | {% extends 'dashboard.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 | 7 |
    8 |
    9 |
    10 |

    工单名:{{ work.name }}

    11 | 12 |
    13 |
    14 |
    15 |

    16 |
    17 | 18 |
    19 | {{ work.db_config }} 20 |
    21 | 22 |
    23 | {{ work.dev }} 24 |
    25 | 26 |
    27 | {% if work.backup == true %}是{% else %}否{% endif %} 28 |
    29 | 30 |
    31 | {% if work.status == 0 %}结束{% elif work.status == 1%}待人工审核{% elif work.status == 2 %}自动审核失败{% elif work.status == 3 %}执行中{% elif work.status == 4 %}执行异常{% elif work.status == 5 %}开发人中止{% elif work.status == 6 %}审核人中止{% elif work.status == 7 %}管理员中止{% elif work.status == 8 %}审核人驳回{% endif %} 32 |
    33 |
    34 | {% if current_user.role == 'dev' %} 35 |
    36 | 37 |
    38 | {% elif current_user.role == 'audit' %} 39 | 40 |
    41 | {% if work.status == 1 %} 42 | 43 | {% if work.srole == 1 %} 44 | 45 | {% elif work.srole == 0 %} 46 | {% if backtimer == 0 %}{% endif %} 47 | 48 | {% endif %} 49 | 50 | 51 | {% endif %} 52 | 53 |
    54 | {% endif %} 55 | 56 | 57 |
    58 |
    59 |
    60 |
    61 |
    62 |
    63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | {% for row in review_content %} 76 | 77 | 78 | 79 | 80 | 85 | 86 | 91 | 92 | 93 | 94 | 95 | 96 | {% endfor %} 97 | 98 |
    IDsql语句自动审核结果扫描影响行数执行耗时执行状态
    {{ row.0 }} 81 | {% for t in row.5.split('\r\n') %} 82 | {{ t }}
    83 | {% endfor %} 84 |
    87 | {% for t in row.4.split('\n') %} 88 | {{ loop.index }}.{{ t }}
    89 | {% endfor %} 90 |
    {{ row.6 }}{{ row.9 }}{{ row.3 }}
    99 | 100 |
    101 |
    102 |

    103 | {% if work.status == 0 and work.backup == true %} 104 | 105 | {% endif %} 106 |
    107 |
    108 |
    109 |
    110 | 111 |
    112 |
    113 |
    114 | 115 | {% endblock %} -------------------------------------------------------------------------------- /app/utils.py: -------------------------------------------------------------------------------- 1 | # -*-coding: utf-8-*- 2 | 3 | import os, re, sys, json, subprocess,time, stat 4 | import MySQLdb 5 | import threading 6 | 7 | reload(sys) 8 | sys.setdefaultencoding('utf-8') 9 | 10 | from app.models import * 11 | from app import app, db, mail 12 | from flask import redirect 13 | from flask_mail import Message 14 | from threading import Thread 15 | from datetime import date, timedelta 16 | import base64 17 | 18 | base_dir = os.path.dirname(__file__) 19 | config = app.config 20 | 21 | mailonoff=config.get('MAIL_ON_OFF') 22 | 23 | inception_host = config.get('INCEPTION_HOST') 24 | inception_port = int(config.get('INCEPTION_PORT')) 25 | 26 | inception_remote_backup_host = config.get('INCEPTION_REMOTE_BACKUP_HOST') 27 | inception_remote_backup_port = int(config.get('INCEPTION_REMOTE_BACKUP_PORT')) 28 | inception_remote_backup_user = config.get('INCEPTION_REMOTE_BACKUP_USER') 29 | inception_remote_backup_password = config.get('INCEPTION_REMOTE_BACKUP_PASSWORD') 30 | 31 | 32 | 33 | def criticalDDL(sqlContent): 34 | ''' 35 | 识别DROP DATABASE, DROP TABLE, TRUNCATE PARTITION, TRUNCATE TABLE等高危DDL操作,因为对于这些操作,inception在备份时只能备份METADATA,而不会备份数据! 36 | 如果识别到包含高危操作,则返回“审核不通过” 37 | ''' 38 | if re.match( 39 | r"([\s\S]*)drop(\s+)database(\s+.*)|([\s\S]*)drop(\s+)table(\s+.*)|([\s\S]*)truncate(\s+)partition(\s+.*)|([\s\S]*)truncate(\s+)table(\s+.*)", 40 | sqlContent.lower()): 41 | return (( 42 | '', '', 2, '', '不能包含【DROP DATABASE】|【DROP TABLE】|【TRUNCATE PARTITION】|【TRUNCATE TABLE】关键字!', '', '', '', 43 | '', ''),) 44 | else: 45 | return None 46 | 47 | 48 | def getAlldbByDbconfig(dbConfigName): 49 | dbConfig = Dbconfig.query.filter(Dbconfig.name == dbConfigName).first() 50 | if not dbConfig: 51 | print("Error: 数据库配置不存在") 52 | dbHost = dbConfig.host 53 | dbPort = dbConfig.port 54 | dbUser = dbConfig.user 55 | dbPassword = base64.b64decode(dbConfig.password) 56 | listDb = [] 57 | conn = None 58 | cursor = None 59 | 60 | try: 61 | conn = MySQLdb.connect(host=dbHost, port=dbPort, user=dbUser, passwd=dbPassword, 62 | charset='utf8') 63 | cursor = conn.cursor() 64 | sql = "show databases" 65 | n = cursor.execute(sql) 66 | listDb = [row[0] for row in cursor.fetchall() 67 | if row[0] not in ('information_schema', 'performance_schema', 'mysql', 'test')] 68 | except MySQLdb.Warning as w: 69 | print(str(w)) 70 | except MySQLdb.Error as e: 71 | print(str(e)) 72 | finally: 73 | if cursor is not None: 74 | cursor.close() 75 | if conn is not None: 76 | conn.commit() 77 | conn.close() 78 | return listDb 79 | 80 | def mysqladvisorcheck(sqlContent, dbConfigName, dbUse): 81 | dbConfig = Dbconfig.query.filter(Dbconfig.name == dbConfigName).first() 82 | if not dbConfig: 83 | print("Error: 数据库配置不存在") 84 | dbHost = dbConfig.host 85 | dbPort = dbConfig.port 86 | dbUser = dbConfig.user 87 | dbPassword = base64.b64decode(dbConfig.password) 88 | p=subprocess.Popen(base_dir+'/sqladvisor/sqladvisor -h '+str(dbHost)+' -P '+str(dbPort)+' -u '+str(dbUser)+' -p '+str(dbPassword)+' -d '+str(dbUse)+' -q "'+str(sqlContent)+'" -v 1', stdin=subprocess.PIPE, stdout=subprocess.PIPE, 89 | stderr=subprocess.PIPE, shell=True) 90 | stdout, stderr = p.communicate() 91 | if stdout: 92 | return stdout 93 | return stderr 94 | def sqlautoReview(sqlContent, dbConfigName, isBackup=False): 95 | ''' 96 | 将sql交给inception进行自动审核,并返回审核结果。 97 | ''' 98 | dbConfig = Dbconfig.query.filter(Dbconfig.name == dbConfigName).first() 99 | if not dbConfig: 100 | print("Error: 数据库配置不存在") 101 | dbHost = dbConfig.host 102 | dbPort = dbConfig.port 103 | dbUser = dbConfig.user 104 | dbPassword = base64.b64decode(dbConfig.password) 105 | 106 | # 这里无需判断字符串是否以;结尾,直接抛给inception enable check即可。 107 | # if sqlContent[-1] != ";": 108 | # sqlContent = sqlContent + ";" 109 | criticalddl = config.get('CRITICAL_DDL_ON_OFF') 110 | if criticalddl == "ON": 111 | criticalDDL_check = criticalDDL(sqlContent) 112 | else: 113 | criticalDDL_check = None 114 | if criticalDDL_check is not None: 115 | result = criticalDDL_check 116 | else: 117 | sql = "/*--user=%s;--password=%s;--host=%s;--enable-check=1;--port=%s;*/\ 118 | inception_magic_start;\ 119 | %s\ 120 | inception_magic_commit;" % (dbUser, dbPassword, dbHost, str(dbPort), sqlContent) 121 | result = fetchall(sql, inception_host, inception_port, '', '', '') 122 | 123 | return result 124 | 125 | 126 | def executeFinal(id): 127 | ''' 128 | 将sql交给inception进行最终执行,并返回执行结果。 129 | ''' 130 | work = Work.query.filter(Work.id == id).first() 131 | if work.status == 3 or work.status == 0: 132 | return redirect('audit_work') 133 | work.status = 3 134 | work.man_review_time = datetime.now() 135 | db.session.commit() 136 | dbConfig = Dbconfig.query.filter(Dbconfig.name == work.db_config).first() 137 | if not dbConfig: 138 | print("Error: 数据库配置不存在") 139 | dbHost = dbConfig.host 140 | dbPort = dbConfig.port 141 | dbUser = dbConfig.user 142 | dbPassword = base64.b64decode(dbConfig.password) 143 | 144 | strBackup = "" 145 | if work.backup == True: 146 | strBackup = "--enable-remote-backup;" 147 | else: 148 | strBackup = "--disable-remote-backup;" 149 | 150 | # 根据inception的要求,执行之前最好先split一下 151 | sqlSplit = "/*--user=%s; --password=%s; --host=%s; --enable-execute;--port=%s; --enable-ignore-warnings;--enable-split;*/\ 152 | inception_magic_start;\ 153 | %s\ 154 | inception_magic_commit;" % (dbUser, dbPassword, dbHost, str(dbPort), work.sql_content) 155 | splitResult = fetchall(sqlSplit, inception_host, inception_port, '', '', '') 156 | tmpList = [] 157 | 158 | # 对于split好的结果,再次交给inception执行.这里无需保持在长连接里执行,短连接即可. 159 | for splitRow in splitResult: 160 | sqlTmp = splitRow[1] 161 | sqlExecute = "/*--user=%s;--password=%s;--host=%s;--enable-execute;--port=%s; --enable-ignore-warnings;%s*/\ 162 | inception_magic_start;\ 163 | %s\ 164 | inception_magic_commit;" % (dbUser, dbPassword, dbHost, str(dbPort), strBackup, sqlTmp) 165 | 166 | executeResult = fetchall(sqlExecute, inception_host, inception_port, '', '', '') 167 | tmpList.append(executeResult) 168 | 169 | # 二次加工一下,目的是为了和sqlautoReview()函数的return保持格式一致,便于在detail页面渲染. 170 | finalStatus = 0 171 | finalList = [] 172 | for splitRow in tmpList: 173 | for sqlRow in splitRow: 174 | # 如果发现任何一个行执行结果里有errLevel为1或2,并且stagestatus列没有包含Execute Successfully字样,则判断最终执行结果为有异常. 175 | if (sqlRow[2] == 1 or sqlRow[2] == 2) and re.match(r"\w*Execute Successfully\w*", sqlRow[3]) is None: 176 | finalStatus = 4 177 | finalList.append(list(sqlRow)) 178 | 179 | jsonResult = json.dumps(finalList) 180 | work.execute_result = jsonResult 181 | work.finish_time = datetime.now() 182 | work.status = finalStatus 183 | db.session.commit() 184 | 185 | 186 | 187 | def getRollbackSqlList(workId): 188 | work = Work.query.filter(Work.id == workId).first() 189 | listExecuteResult = json.loads(work.execute_result) 190 | listBackupSql = [] 191 | for row in listExecuteResult: 192 | # 获取backup_dbname 193 | if row[8] == 'None': 194 | continue; 195 | backupDbName = row[8] 196 | sequence = row[7] 197 | opidTime = sequence.replace("'", "") 198 | sqlTable = "select tablename from %s.$_$Inception_backup_information$_$ where opid_time='%s';" % ( 199 | backupDbName, opidTime) 200 | listTables = fetchall(sqlTable, inception_remote_backup_host, inception_remote_backup_port, 201 | inception_remote_backup_user, inception_remote_backup_password, '') 202 | if listTables is None or len(listTables) != 1: 203 | print("Error: returned listTables more than 1.") 204 | 205 | tableName = listTables[0][0] 206 | sqlBack = "select rollback_statement from %s.%s where opid_time='%s'" % (backupDbName, tableName, opidTime) 207 | listBackup = fetchall(sqlBack, inception_remote_backup_host, inception_remote_backup_port, 208 | inception_remote_backup_user, inception_remote_backup_password, '') 209 | if listBackup is not None and len(listBackup) != 0: 210 | for rownum in range(len(listBackup)): 211 | listBackupSql.append(listBackup[rownum][0]) 212 | return listBackupSql 213 | 214 | def getSlowLogList(dbId, hour): 215 | dbDt=(datetime.now()-timedelta(hours=hour)).strftime('%Y-%m-%d %H:%M:%S') 216 | dbConfig=Dbconfig.query.filter(Dbconfig.id == dbId).first() 217 | 218 | sql="select sql_text,count(sql_text) c from mysql.slow_log where start_time >= '%s' group by sql_text order by c asc limit 30" % (dbDt) 219 | slowlogList=fetchall(sql, dbConfig.host, dbConfig.port, 220 | dbConfig.user, base64.b64decode(dbConfig.password), '') 221 | return slowlogList 222 | 223 | def getdbReport(dbId, mem): 224 | dbConfig = Dbconfig.query.get(dbId) 225 | p = subprocess.Popen('perl '+base_dir+'/mysqltuner.pl --host '+str(dbConfig.host)+' --user '+str(dbConfig.user)+' --pass '+str(base64.b64decode(dbConfig.password))+' --port '+str(dbConfig.port)+' --forcemem '+str(mem), stdin=subprocess.PIPE, stdout=subprocess.PIPE, 226 | stderr=subprocess.PIPE, shell=True) 227 | stdout, stderr = p.communicate() 228 | dbReport='' 229 | if stdout: 230 | stdout = stdout.replace('[\x1b[0;34m', '[') 231 | stdout = stdout.replace('\x1b[0m]', ']') 232 | stdout = stdout.replace('[\x1b[0;32m', '[') 233 | stdout = stdout.replace('[\x1b[0;31m', '[') 234 | stdout = stdout.replace('\x1b[0;32m', ' ') 235 | stdout = stdout.replace('\x1b[0m\x1b[0;32m', ' ') 236 | stdout = stdout.replace('\x1b[0m', '') 237 | stdout = stdout.replace('\x1b[0;31m', '') 238 | dbReport = stdout 239 | else: 240 | print u'错误:'+stderr 241 | return dbReport 242 | 243 | 244 | 245 | 246 | def fetchall(sql, paramHost, paramPort, paramUser, paramPasswd, paramDb): 247 | ''' 248 | 封装mysql连接和获取结果集方法 249 | ''' 250 | result = None 251 | conn = None 252 | cur = None 253 | sql = sql.encode('utf-8') 254 | 255 | try: 256 | conn = MySQLdb.connect(host=paramHost, user=paramUser, passwd=paramPasswd, db=paramDb, port=paramPort) 257 | conn.set_character_set('utf8') 258 | cur = conn.cursor() 259 | ret = cur.execute(sql) 260 | result = cur.fetchall() 261 | result = result 262 | except MySQLdb.Error as e: 263 | print("Mysql Error %d: %s" % (e.args[0], e.args[1])) 264 | finally: 265 | if cur is not None: 266 | cur.close() 267 | if conn is not None: 268 | conn.close() 269 | return result 270 | 271 | def send_async_email(app, msg): 272 | with app.app_context(): 273 | mail.send(msg) 274 | def send_email(subject, body, receiver): 275 | msg = Message(subject, recipients=[receiver]) 276 | msg.html = body 277 | thr = Thread(target=send_async_email, args=[app, msg]) 278 | thr.start() 279 | return u'发送成功' 280 | def checksqladvisor(): 281 | sqladvisordir = base_dir + '/sqladvisor' 282 | if not os.path.exists(sqladvisordir): 283 | os.makedirs(sqladvisordir) 284 | if os.path.exists(sqladvisordir+'/sqladvisor'): 285 | installtime = time.localtime(os.path.getmtime(sqladvisordir + '/sqladvisor')) 286 | installtime = time.strftime('%Y-%m-%d %H:%M:%S', installtime) 287 | return u'SQLAdvisor已安装,安装时间:' +str(installtime) 288 | else: 289 | return u'SQLAdvisor未安装' 290 | 291 | def stoptimer(work): 292 | for item in threading.enumerate(): 293 | if item.name == work.name: 294 | item.cancel() 295 | def starttimer(work, executetime): 296 | t = threading.Timer(executetime, executeFinal, [work.id]) 297 | t.name = work.name 298 | t.setDaemon(True) 299 | t.start() 300 | 301 | 302 | 303 | 304 | -------------------------------------------------------------------------------- /config_example.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Flask settings 4 | SECRET_KEY = os.getenv('SECRET_KEY', 'THIS IS AN INSECURE SECRET') 5 | SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL', 'mysql://inception_web:inception_web@192.168.10.152:3306/inception_web?charset=utf8') 6 | SQLALCHEMY_TRACK_MODIFICATIONS=False 7 | CSRF_ENABLED = True 8 | 9 | 10 | # Flask-Mail settings 11 | MAIL_ON_OFF='ON' 12 | MAIL_SERVER='smtp.qq.com' 13 | MAIL_PORT=465 14 | MAIL_USE_TLS=False 15 | MAIL_USE_SSL=True 16 | MAIL_USERNAME='cljqqyx@qq.com' 17 | MAIL_PASSWORD='xxx' 18 | MAIL_DEFAULT_SENDER='"test"' 19 | 20 | 21 | #Inception settings 22 | 23 | INCEPTION_HOST='192.168.10.30' 24 | INCEPTION_PORT=6669 25 | 26 | #Inception backup settings 27 | INCEPTION_REMOTE_BACKUP_HOST='192.168.10.152' 28 | INCEPTION_REMOTE_BACKUP_PORT=3306 29 | INCEPTION_REMOTE_BACKUP_USER='inception_web' 30 | INCEPTION_REMOTE_BACKUP_PASSWORD='inception_web' 31 | 32 | #Other optins 33 | CRITICAL_DDL_ON_OFF='OFF' 34 | AUDIT_SROLE_ON_OFF='OFF' 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /debug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | CURDIR=$(cd `dirname $0`; pwd) 3 | CURUSER=`whoami` 4 | su - $CURUSER -c "cd $CURDIR&&python run.py" 5 | -------------------------------------------------------------------------------- /doc/images/SQLAdvisor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/doc/images/SQLAdvisor.png -------------------------------------------------------------------------------- /doc/images/发起sql工单.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/doc/images/发起sql工单.png -------------------------------------------------------------------------------- /doc/images/工单图表.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/doc/images/工单图表.png -------------------------------------------------------------------------------- /doc/images/工单处理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/doc/images/工单处理.png -------------------------------------------------------------------------------- /doc/images/工单查询.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/doc/images/工单查询.png -------------------------------------------------------------------------------- /doc/images/待审核工单.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/doc/images/待审核工单.png -------------------------------------------------------------------------------- /doc/images/登陆页.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/doc/images/登陆页.png -------------------------------------------------------------------------------- /doc/images/管理员主页.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/doc/images/管理员主页.png -------------------------------------------------------------------------------- /doc/images/配置报告.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/496080199/inception_web/3baff6aa0d1e1d2815a91d82e326d8f4325cfeda/doc/images/配置报告.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask==0.12.2 2 | flask-mail==0.9.1 3 | flask-sqlalchemy==2.2 4 | flask-principal==0.4.0 5 | flask-login==0.4.0 6 | MySQL-python==1.2.5 7 | flask-wtf==0.14.2 -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | 2 | from app import app 3 | 4 | 5 | 6 | 7 | if __name__ == "__main__": 8 | app.run(host='0.0.0.0', debug=True) 9 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gunicorn -w 1 -b 0.0.0.0:5000 app:app & 4 | -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | ps -ef|grep gunicorn|grep 5000|awk '{print $2}'|xargs kill -15 5 | --------------------------------------------------------------------------------