├── logs └── .gitkeep ├── common ├── __init__.py ├── utils │ ├── __init__.py │ ├── global_info.py │ ├── timer.py │ ├── wx_api.py │ ├── extend_json_encoder.py │ ├── permission.py │ ├── aes_decryptor.py │ ├── const.py │ └── aliyun_sdk.py ├── middleware │ ├── __init__.py │ ├── exception_logging_middleware.py │ └── check_login_middleware.py ├── templates │ ├── error.html │ └── errors │ │ ├── 400.html │ │ ├── 403.html │ │ ├── 500.html │ │ └── 404.html ├── static │ ├── bootstrap-editable │ │ └── img │ │ │ ├── clear.png │ │ │ └── loading.gif │ ├── font-awesome │ │ └── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ ├── bootstrap-fileinput │ │ ├── img │ │ │ └── loading.gif │ │ └── js │ │ │ └── locales │ │ │ └── zh.js │ ├── bootstrap │ │ └── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ ├── ace │ │ ├── mode-text.js │ │ ├── snippets │ │ │ ├── text.js │ │ │ ├── pgsql.js │ │ │ ├── mysql.js │ │ │ ├── sql.js │ │ │ └── sqlserver.js │ │ ├── mode-sql.js │ │ ├── theme-github.js │ │ ├── theme-textmate.js │ │ ├── theme-sqlserver.js │ │ └── theme-mongodb.js │ ├── dist │ │ ├── css │ │ │ └── login.css │ │ └── js │ │ │ └── formatter.js │ ├── datetimepicker │ │ └── js │ │ │ └── bootstrap-datetimepicker.zh-CN.js │ ├── sb-admin-2 │ │ └── js │ │ │ ├── sb-admin-2.min.js │ │ │ └── sb-admin-2.js │ ├── bootstrap-select │ │ └── js │ │ │ └── i18n │ │ │ ├── defaults-zh_CN.min.js │ │ │ ├── defaults-zh_CN.js.map │ │ │ └── defaults-zh_CN.js │ ├── bootstrap-table │ │ ├── export-libs │ │ │ ├── FileSaver │ │ │ │ ├── LICENSE.md │ │ │ │ └── FileSaver.min.js │ │ │ ├── html2canvas │ │ │ │ └── LICENSE │ │ │ ├── pdfmake │ │ │ │ └── LICENSE │ │ │ ├── es6-promise │ │ │ │ └── LICENSE │ │ │ ├── jsPDF │ │ │ │ └── MIT-LICENSE.txt │ │ │ └── jsPDF-AutoTable │ │ │ │ └── LICENSE.txt │ │ ├── js │ │ │ └── bootstrap-table-zh-CN.min.js │ │ └── extensions │ │ │ └── editable │ │ │ └── bootstrap-table-editable.min.js │ └── metisMenu │ │ └── css │ │ └── metisMenu.min.css ├── storage.py ├── views.py ├── workflow.py └── config.py ├── sql ├── __init__.py ├── plugins │ ├── __init__.py │ ├── sqladvisor.py │ ├── schemasync.py │ ├── pt_archiver.py │ ├── plugin.py │ └── binglog2sql.py ├── static │ ├── css │ │ ├── .gitkeep │ │ └── basic.css │ ├── js │ │ └── .gitkeep │ └── pics │ │ └── .gitkeep ├── utils │ ├── __init__.py │ ├── tasks.py │ ├── resource_group.py │ ├── execute_sql.py │ └── sql_utils.py ├── templatetags │ ├── __init__.py │ └── format_tags.py ├── locale │ ├── en │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ ├── djangojs.mo │ │ │ ├── django.po │ │ │ └── djangojs.po │ └── zh_Hans │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ ├── djangojs.mo │ │ ├── django.po │ │ └── djangojs.po ├── templates │ └── dbaprinciples.html ├── completer │ ├── __init__.py │ ├── tests.py │ └── mysql.py ├── fixtures │ └── auth_group.sql ├── engines │ └── mongo.py └── sql_analyze.py ├── CONTRIBUTING.md ├── sql_api ├── __init__.py ├── apps.py ├── urls.py └── tests.py ├── downloads ├── binlog2sql │ └── .gitkeep └── schemasync │ └── .gitkeep ├── src ├── docker-compose │ ├── archery │ │ ├── downloads │ │ │ ├── log │ │ │ │ └── .gitkeep │ │ │ ├── binlog2sql │ │ │ │ └── .gitkeep │ │ │ └── schemasync │ │ │ │ └── .gitkeep │ │ ├── docs.md │ │ └── soar.yaml │ ├── inception │ │ └── inc.cnf │ ├── mysql │ │ └── my.cnf │ └── docker-compose.yml ├── charts │ ├── Chart.yaml │ ├── charts │ │ ├── inception │ │ │ ├── Chart.yaml │ │ │ ├── .helmignore │ │ │ ├── templates │ │ │ │ ├── configMap.yaml │ │ │ │ ├── service.yaml │ │ │ │ ├── _helpers.tpl │ │ │ │ ├── ingress.yaml │ │ │ │ ├── NOTES.txt │ │ │ │ └── deployment.yaml │ │ │ └── values.yaml │ │ └── goinception │ │ │ ├── Chart.yaml │ │ │ ├── .helmignore │ │ │ └── templates │ │ │ ├── configMap.yaml │ │ │ ├── service.yaml │ │ │ ├── _helpers.tpl │ │ │ ├── ingress.yaml │ │ │ ├── NOTES.txt │ │ │ └── deployment.yaml │ ├── requirements.yaml │ ├── templates │ │ ├── configMap.yaml │ │ ├── service.yaml │ │ ├── _helpers.tpl │ │ ├── ingress.yaml │ │ ├── NOTES.txt │ │ └── deployment.yaml │ └── README.md ├── init_sql │ ├── v1.3.6_v1.3.7.sql │ ├── v1.6.5_v1.6.6.sql │ ├── v1.6.0_v1.6.1.sql │ ├── v1.7.7_v1.7.8.sql │ ├── v1.6.2_v1.6.3.sql │ ├── v1.6.6_v1.6.7.sql │ ├── v1.7.1_v1.7.2.sql │ ├── v1.3.8_v1.4.0.sql │ ├── v1.7.2_v1.7.3.sql │ ├── v1.3.0_v1.3.2.sql │ ├── v1.7.0_v1.7.1.sql │ ├── v1.4.2_v1.4.3.sql │ ├── v1.7.6_v1.7.7.sql │ ├── del_permissions.sql │ ├── v1.0_v1.1.0.sql │ ├── v1.2.0_v1.3.0.sql │ ├── v1.6.1_v1.6.2.sql │ ├── v1.6.7_v1.7.0.sql │ ├── v1.5.3_v1.6.0.sql │ ├── v1.7.4_v1.7.5.sql │ └── v1.4.5_v1.5.0.sql ├── docker │ ├── supervisord.conf │ ├── Dockerfile │ ├── startup.sh │ ├── nginx.conf │ └── Dockerfile-base ├── plugins │ └── soar.yaml └── script │ └── analysis_slow_query.sh ├── archery ├── __init__.py ├── wsgi.py └── urls.py ├── .gitattributes ├── debug.sh ├── .coveragerc ├── startup.sh ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── custom.md │ └── bug.md ├── config.yml ├── release-drafter.yml └── stale.yml ├── manage.py ├── requirements.txt ├── .travis.yml ├── supervisord.conf ├── admin.sh └── CODE_OF_CONDUCT.md /logs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sql/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sql/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sql/static/css/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sql/static/js/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sql/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sql_api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /common/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sql/static/pics/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sql/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /common/middleware/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /downloads/binlog2sql/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /downloads/schemasync/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/docker-compose/archery/downloads/log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/docker-compose/archery/downloads/binlog2sql/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/docker-compose/archery/downloads/schemasync/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/docker-compose/archery/docs.md: -------------------------------------------------------------------------------- 1 | # 请替换docs目录下的Markdown文件 2 | -------------------------------------------------------------------------------- /archery/__init__.py: -------------------------------------------------------------------------------- 1 | version = (1, 7, 8) 2 | display_version = '.'.join(str(i) for i in version) 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=python 2 | *.css linguist-language=python 3 | *.html linguist-language=python 4 | -------------------------------------------------------------------------------- /sql/locale/en/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/sql/locale/en/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /sql/locale/en/LC_MESSAGES/djangojs.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/sql/locale/en/LC_MESSAGES/djangojs.mo -------------------------------------------------------------------------------- /sql_api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SqlApi2Config(AppConfig): 5 | name = 'sql_api' 6 | -------------------------------------------------------------------------------- /sql/locale/zh_Hans/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/sql/locale/zh_Hans/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /common/templates/error.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

{{errMsg}}

5 | {% endblock content %} 6 | -------------------------------------------------------------------------------- /sql/locale/zh_Hans/LC_MESSAGES/djangojs.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/sql/locale/zh_Hans/LC_MESSAGES/djangojs.mo -------------------------------------------------------------------------------- /src/charts/Chart.yaml: -------------------------------------------------------------------------------- 1 | aiVersion: v1 2 | appVersion: "1.0" 3 | description: A Helm chart for Kubernetes 4 | name: archery 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /src/init_sql/v1.3.6_v1.3.7.sql: -------------------------------------------------------------------------------- 1 | -- 修改阿里云配置信息表 2 | alter table aliyun_rds_config add is_enable tinyint not null default 0 comment '是否启用'; 3 | -------------------------------------------------------------------------------- /common/static/bootstrap-editable/img/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/common/static/bootstrap-editable/img/clear.png -------------------------------------------------------------------------------- /common/static/bootstrap-editable/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/common/static/bootstrap-editable/img/loading.gif -------------------------------------------------------------------------------- /common/static/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/common/static/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /common/static/bootstrap-fileinput/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/common/static/bootstrap-fileinput/img/loading.gif -------------------------------------------------------------------------------- /common/templates/errors/400.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Bad Request (400)

5 | {% endblock content %} 6 | -------------------------------------------------------------------------------- /common/templates/errors/403.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

403 Forbidden

5 | {% endblock content %} 6 | -------------------------------------------------------------------------------- /common/templates/errors/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Server Error (500)

5 | {% endblock content %} 6 | -------------------------------------------------------------------------------- /debug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | nohup python3 manage.py runserver 0.0.0.0:9123 --insecure & 4 | 5 | # 启动Django Q cluster 6 | nohup python3 manage.py qcluster & 7 | -------------------------------------------------------------------------------- /src/charts/charts/inception/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "1.0" 3 | description: A Helm chart for Kubernetes 4 | name: inception 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | include = 3 | common* 4 | sql* 5 | sql_api* 6 | omit = 7 | src* 8 | downloads* 9 | sql/migrations/* 10 | 11 | -------------------------------------------------------------------------------- /common/static/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/common/static/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /common/static/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/common/static/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /common/static/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/common/static/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /common/static/font-awesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/common/static/font-awesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/charts/charts/goinception/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "1.0" 3 | description: A Helm chart for Kubernetes 4 | name: goinception 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 收集所有的静态文件到STATIC_ROOT 4 | python3 manage.py collectstatic -v0 --noinput 5 | 6 | # 启动服务 7 | supervisord -c supervisord.conf 8 | 9 | -------------------------------------------------------------------------------- /common/static/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/common/static/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /common/static/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/common/static/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /common/static/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/common/static/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /common/static/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/Archery/master/common/static/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/init_sql/v1.6.5_v1.6.6.sql: -------------------------------------------------------------------------------- 1 | -- 增加查询语句收藏/置顶功能 2 | alter table query_log 3 | add favorite tinyint not null default 0 comment '是否收藏', 4 | add alias varchar(100) not null default '' comment '语句标识/别名'; 5 | -------------------------------------------------------------------------------- /common/templates/errors/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Not Found

5 |

The requested resource was not found on this server.

6 | {% endblock content %} 7 | -------------------------------------------------------------------------------- /sql_api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from sql_api import views 3 | 4 | urlpatterns = [ 5 | path('info', views.info), 6 | path('debug', views.debug), 7 | path('do_once/mirage', views.mirage) 8 | ] 9 | -------------------------------------------------------------------------------- /src/init_sql/v1.6.0_v1.6.1.sql: -------------------------------------------------------------------------------- 1 | -- 扩充数据库类型字段 2 | alter table sql_instance modify db_type varchar(20) not null default '' comment '数据库类型'; 3 | alter table param_template modify db_type varchar(20) not null default '' comment '数据库类型'; 4 | -------------------------------------------------------------------------------- /src/init_sql/v1.7.7_v1.7.8.sql: -------------------------------------------------------------------------------- 1 | -- 增加工单备注长度 2 | alter table workflow_audit_detail modify remark varchar(1000) NOT NULL DEFAULT '' COMMENT '审核备注'; 3 | alter table workflow_log modify operation_info varchar(1000) NOT NULL DEFAULT '' COMMENT '操作信息'; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | *.lock 4 | *.log 5 | .idea/ 6 | .DS_Store 7 | archery/settings.py.github 8 | archery/settings.py.dev 9 | archery/settings_dev.py 10 | sql/migrations/ 11 | venv 12 | env 13 | sonar-project.properties 14 | .scannerwork -------------------------------------------------------------------------------- /src/init_sql/v1.6.2_v1.6.3.sql: -------------------------------------------------------------------------------- 1 | -- 增加数据字典权限 2 | set @content_type_id=(select id from django_content_type where app_label='sql' and model='permission'); 3 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('菜单 数据字典', @content_type_id, 'menu_data_dictionary'); 4 | -------------------------------------------------------------------------------- /src/init_sql/v1.6.6_v1.6.7.sql: -------------------------------------------------------------------------------- 1 | -- 增加实例用户列表字典权限 2 | set @content_type_id=(select id from django_content_type where app_label='sql' and model='permission'); 3 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('菜单 实例用户列表', @content_type_id, 'menu_instance_user'); 4 | -------------------------------------------------------------------------------- /src/init_sql/v1.7.1_v1.7.2.sql: -------------------------------------------------------------------------------- 1 | -- 增加导出数据字典权限 2 | set @content_type_id=(select id from django_content_type where app_label='sql' and model='permission'); 3 | INSERT IGNORE INTO auth_permission (name, content_type_id, codename) VALUES ('导出数据字典', @content_type_id, 'data_dictionary_export'); 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 功能建议 3 | about: 如果你对Archery有什么功能或者改进建议,请在此反馈,也欢迎直接向我们提交pr 4 | title: '[ 功能建议 ]' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### 目前遇到的问题/使用障碍 11 | 12 | ### 希望如何解决/实现它 13 | 14 | ### 其他信息 15 | 如果有其他类似的产品功能或者图片信息,可在此提交 16 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "archery.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /sql/templatetags/format_tags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | from django import template 3 | from django.utils.safestring import mark_safe 4 | 5 | register = template.Library() 6 | 7 | 8 | # 替换换行符 9 | @register.simple_tag 10 | def format_str(str): 11 | # 换行 12 | return mark_safe(str.replace(',', '
').replace('\n', '
')) 13 | -------------------------------------------------------------------------------- /common/static/ace/mode-text.js: -------------------------------------------------------------------------------- 1 | 2 | ; (function() { 3 | window.require(["ace/mode/text"], function(m) { 4 | if (typeof module == "object" && typeof exports == "object" && module) { 5 | module.exports = m; 6 | } 7 | }); 8 | })(); 9 | -------------------------------------------------------------------------------- /common/middleware/exception_logging_middleware.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | import logging 3 | from django.utils.deprecation import MiddlewareMixin 4 | 5 | logger = logging.getLogger('default') 6 | 7 | 8 | class ExceptionLoggingMiddleware(MiddlewareMixin): 9 | def process_exception(self, request, exception): 10 | import traceback 11 | logger.error(traceback.format_exc()) 12 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | # Configuration for request-info - https://github.com/behaviorbot/request-info 2 | 3 | # *Required* Comment to reply with 4 | requestInfoReplyComment: > 5 | 你好!感谢你反馈的问题/bug,但是你的描述好像是空的,我们需要你完整的信息,这样才能帮你解决问题 6 | 如果不知道怎么写,在新建issue的时候有若干个模板可供选择,祝好! 7 | 8 | # *OPTIONAL* Label to be added to Issues and Pull Requests with insufficient information given 9 | requestInfoLabelToAdd: 请提供更多信息 10 | -------------------------------------------------------------------------------- /archery/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for archery project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "archery.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /src/charts/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: redis 3 | version: 1.1.15 4 | repository: https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts 5 | # condition: redis.usePassword="",redis.persistence.enabled=false 6 | - name: mysql 7 | version: 0.3.5 8 | repository: https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts 9 | # condition: mysql.mysqlRootPassword="archery",mysql.mysqlDatabase="archery",persistence.enabled=false 10 | -------------------------------------------------------------------------------- /src/charts/charts/inception/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | .vscode/ 23 | -------------------------------------------------------------------------------- /src/init_sql/v1.3.8_v1.4.0.sql: -------------------------------------------------------------------------------- 1 | -- 新增登录失败信息 2 | alter table sql_users 3 | add failed_login_count tinyint not null default 0 comment '失败计数', 4 | add last_login_failed_at timestamp comment '上次失败登录时间'; 5 | 6 | -- 修改资源表名 7 | rename table sql_group to resource_group; 8 | rename table sql_group_relations to resource_group_relations; 9 | 10 | -- 使用django_q替换django_apscheduler 11 | drop table django_apscheduler_djangojobexecution; 12 | drop table django_apscheduler_djangojob; 13 | -------------------------------------------------------------------------------- /src/charts/charts/goinception/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | .vscode/ 23 | -------------------------------------------------------------------------------- /archery/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path 2 | from django.contrib import admin 3 | from common import views 4 | 5 | urlpatterns = [ 6 | path('admin/', admin.site.urls), 7 | path('api/', include(('sql_api.urls', 'sql_api'), namespace="sql_api")), 8 | path('', include(('sql.urls', 'sql'), namespace="sql")), 9 | ] 10 | 11 | handler400 = views.bad_request 12 | handler403 = views.permission_denied 13 | handler404 = views.page_not_found 14 | handler500 = views.server_error 15 | -------------------------------------------------------------------------------- /common/static/ace/snippets/text.js: -------------------------------------------------------------------------------- 1 | define("ace/snippets/text",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="text"}); (function() { 2 | window.require(["ace/snippets/text"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /src/docker/supervisord.conf: -------------------------------------------------------------------------------- 1 | [unix_http_server] 2 | file=/tmp/supervisor.sock 3 | 4 | [supervisord] 5 | logfile=logs/supervisord.log 6 | nodaemon=false 7 | 8 | [supervisorctl] 9 | serverurl=unix:///tmp/supervisor.sock 10 | 11 | [rpcinterface:supervisor] 12 | supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface 13 | 14 | [program:qcluster] 15 | command=python manage.py qcluster 16 | autorestart=true 17 | stopasgroup=true 18 | killasgroup=true 19 | redirect_stderr=true 20 | 21 | -------------------------------------------------------------------------------- /common/static/ace/snippets/pgsql.js: -------------------------------------------------------------------------------- 1 | define("ace/snippets/pgsql",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="pgsql"}); (function() { 2 | window.require(["ace/snippets/pgsql"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /src/init_sql/v1.7.2_v1.7.3.sql: -------------------------------------------------------------------------------- 1 | -- 修改工具插件的菜单code 2 | UPDATE auth_permission SET codename='menu_tools' WHERE codename='menu_menu_tools'; 3 | 4 | -- SQL上线工单增加需求链接 5 | ALTER TABLE sql_workflow ADD demand_url varchar(500) NOT NULL DEFAULT '' COMMENT '需求链接'; 6 | 7 | -- 增加事务查看权限 8 | set @content_type_id=(select id from django_content_type where app_label='sql' and model='permission'); 9 | INSERT IGNORE INTO auth_permission (name, content_type_id, codename) VALUES ('查看事务信息', @content_type_id, 'trx_view'); 10 | 11 | -------------------------------------------------------------------------------- /src/charts/templates/configMap.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.configMap.enabled -}} 2 | kind: ConfigMap 3 | apiVersion: v1 4 | metadata: 5 | name: archery-config 6 | labels: 7 | app.kubernetes.io/name: {{ include "archery.name" . }} 8 | helm.sh/chart: {{ include "archery.chart" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | {{- with .Values.configMap.data }} 12 | data: 13 | {{- toYaml . | nindent 2 }} 14 | {{- end -}} 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==2.2.10 2 | mysqlclient==1.4.6 3 | requests==2.22.0 4 | simplejson==3.17.0 5 | mybatis_mapper2sql==0.1.9 6 | django-auth-ldap==2.1.0 7 | python-dateutil==2.8.1 8 | pymongo==3.9.0 9 | psycopg2-binary==2.8.4 10 | mysql-replication==0.21 11 | django-q==1.0.2 12 | django-redis==4.10.0 13 | pyodbc==4.0.27 14 | gunicorn==20.0.4 15 | pyecharts==1.6.0 16 | aliyun-python-sdk-rds==2.1.1 17 | cx-Oracle==7.3.0 18 | supervisor==4.1.0 19 | gevent==1.4.0 20 | phoenixdb==0.7 21 | django-mirage-field==1.0.1 22 | schema-sync==0.9.7 23 | -------------------------------------------------------------------------------- /src/charts/charts/inception/templates/configMap.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.configMap.enabled -}} 2 | kind: ConfigMap 3 | apiVersion: v1 4 | metadata: 5 | name: inception-config 6 | labels: 7 | app.kubernetes.io/name: {{ include "inception.name" . }} 8 | helm.sh/chart: {{ include "inception.chart" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | {{- with .Values.configMap.data }} 12 | data: 13 | {{- toYaml . | nindent 2 }} 14 | {{- end }} 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /src/charts/charts/goinception/templates/configMap.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.configMap.enabled -}} 2 | kind: ConfigMap 3 | apiVersion: v1 4 | metadata: 5 | name: goinception-config 6 | labels: 7 | app.kubernetes.io/name: {{ include "goinception.name" . }} 8 | helm.sh/chart: {{ include "goinception.chart" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | {{- with .Values.configMap.data }} 12 | data: 13 | {{- toYaml . | nindent 2 }} 14 | {{- end }} 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /common/utils/global_info.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | from sql.utils.workflow_audit import Audit 3 | from archery import display_version 4 | 5 | 6 | def global_info(request): 7 | """存放用户,菜单信息等.""" 8 | user = request.user 9 | if user and user.is_authenticated: 10 | # 获取待办数量 11 | try: 12 | todo = Audit.todo(user) 13 | except Exception: 14 | todo = 0 15 | else: 16 | todo = 0 17 | 18 | return { 19 | 'todo': todo, 20 | 'archery_version': display_version 21 | } 22 | -------------------------------------------------------------------------------- /common/static/dist/css/login.css: -------------------------------------------------------------------------------- 1 | .user-bottom-div { 2 | display: block; 3 | text-align: center; 4 | background-color: #ddd; 5 | position: fixed; 6 | bottom: 0; 7 | width: 100%; 8 | height: 50px; 9 | line-height: 50px; 10 | } 11 | 12 | .lsb-login { 13 | display: flex; 14 | justify-content: center; 15 | align-items: center; 16 | height: 90vh; 17 | } 18 | 19 | .mypanalbox { 20 | background-color: #FFF; 21 | border-radius: 8px; 22 | box-shadow: 3px 3px 3px; 23 | } 24 | 25 | .login-form { 26 | margin: 20px; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /common/static/ace/snippets/mysql.js: -------------------------------------------------------------------------------- 1 | define("ace/snippets/mysql",["require","exports","module"], function(require, exports, module) { 2 | "use strict"; 3 | 4 | exports.snippetText =undefined; 5 | exports.scope = "mysql"; 6 | 7 | }); (function() { 8 | window.require(["ace/snippets/mysql"], function(m) { 9 | if (typeof module == "object" && typeof exports == "object" && module) { 10 | module.exports = m; 11 | } 12 | }); 13 | })(); 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 问题咨询 3 | about: 如果在使用过程中遇到问题,在查阅文档后仍无法解决,可以在此反馈 4 | title: '[ 问题咨询 ]' 5 | labels: 'question' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### 问题描述 11 | 详细描述你的操作步骤和结果,以及你的疑问 12 | 13 | ### 版本信息 14 | - 应用版本/分支:Release v1.4.5 15 | - 部署方式:Docker、手工部署 16 | 17 | 21 | 22 | 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6.5" 4 | before_install: 5 | - sudo apt-get install unixodbc unixodbc-dev 6 | services: 7 | - mysql 8 | - redis-server 9 | install: 10 | - pip install codecov coverage flake8 mycli==1.19.0 -r requirements.txt 11 | before_script: 12 | - flake8 . --count --exclude=./.* --select=E9,F63,F7,F82 --show-source --statistics 13 | - mysql -e "CREATE DATABASE archery CHARSET UTF8;" 14 | - python manage.py makemigrations 15 | - python manage.py makemigrations sql 16 | script: 17 | - coverage run manage.py test -v 3 18 | 19 | after_success: 20 | - codecov 21 | -------------------------------------------------------------------------------- /src/charts/README.md: -------------------------------------------------------------------------------- 1 | 1、setting: 根目录values.yaml下配置,相关configMap的文件,settings.py,soar.yaml,analysis_slow_query.sh等,找出与mysql,redis的数据库连接 charts/goinception,charts/inception目录下的values.yaml配置修改,主要与mysql连接的配置 mysql的存储持久化,请查看values.yaml的方法进行配置 2 | 3 | 2、dependency: cd charts/archeryk8s && helm dependency update 4 | 5 | 3、install: helm install ./ --name archery --namespace=default 6 | 7 | 4、visit: 8 | 9 | i 本机访问 kubectl port-forward pods/archery-xxxxxx 9123:9123 10 | ii 集群外访问 将svc配置为nodePort或loadBalance,或开启ingress 11 | 12 | 默认关闭ingress,如需开启ingress,请在values.yaml设置为true,并修改ingress默认域名配置。 13 | 14 | 15 | 默认用户名:admin 16 | 默认密码:Archery2019 17 | 18 | -------------------------------------------------------------------------------- /src/init_sql/v1.3.0_v1.3.2.sql: -------------------------------------------------------------------------------- 1 | -- 修改查询日志表 2 | ALTER TABLE query_log 3 | ADD priv_check TINYINT NOT NULL DEFAULT 0 4 | COMMENT '查询权限是否正常校验,1, 正常, 2, 跳过' 5 | AFTER user_display, 6 | ADD hit_rule TINYINT NOT NULL DEFAULT 0 7 | COMMENT '查询是否命中脱敏规则,0,未知, 1, 命中, 2,未命中' 8 | AFTER priv_check, 9 | ADD masking TINYINT NOT NULL DEFAULT 0 10 | COMMENT '查询结果是否正常脱敏,1, 是, 2, 否' 11 | AFTER hit_rule; 12 | 13 | -- 增加菜单权限 14 | set @content_type_id=(select id from django_content_type where app_label='sql' and model='permission'); 15 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('菜单 SchemaSync', @content_type_id, 'menu_schemasync'); 16 | -------------------------------------------------------------------------------- /common/utils/timer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @author: hhyo 4 | @license: Apache Licence 5 | @file: timer.py 6 | @time: 2019/05/15 7 | """ 8 | import datetime 9 | 10 | __author__ = 'hhyo' 11 | 12 | 13 | class FuncTimer(object): 14 | """ 15 | 获取执行时间的上下文管理器 16 | """ 17 | 18 | def __init__(self): 19 | self.start = None 20 | self.end = None 21 | self.cost = 0 22 | 23 | def __enter__(self): 24 | self.start = datetime.datetime.now() 25 | return self 26 | 27 | def __exit__(self, exc_type, exc_val, exc_tb): 28 | self.end = datetime.datetime.now() 29 | self.cost = (self.end - self.start).total_seconds() 30 | -------------------------------------------------------------------------------- /supervisord.conf: -------------------------------------------------------------------------------- 1 | [unix_http_server] 2 | file=supervisor.sock 3 | 4 | [supervisord] 5 | logfile=logs/supervisord.log 6 | nodaemon=false 7 | 8 | [supervisorctl] 9 | serverurl=unix://supervisor.sock 10 | 11 | [rpcinterface:supervisor] 12 | supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface 13 | 14 | [program:archery] 15 | command=gunicorn -w 4 -k gevent -b 127.0.0.1:8000 --timeout 600 archery.wsgi:application 16 | autorestart=true 17 | stopasgroup=true 18 | killasgroup=true 19 | redirect_stderr=true 20 | 21 | [program:qcluster] 22 | command=python manage.py qcluster 23 | autorestart=true 24 | stopasgroup=true 25 | killasgroup=true 26 | redirect_stderr=true 27 | 28 | -------------------------------------------------------------------------------- /common/storage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @author: hhyo 4 | @license: Apache Licence 5 | @file: storage.py 6 | @time: 2019/06/01 7 | """ 8 | 9 | __author__ = 'hhyo' 10 | 11 | from django.contrib.staticfiles.storage import ManifestStaticFilesStorage 12 | 13 | 14 | class ForgivingManifestStaticFilesStorage(ManifestStaticFilesStorage): 15 | manifest_strict = False 16 | 17 | def hashed_name(self, name, content=None, filename=None): 18 | try: 19 | result = super().hashed_name(name, content, filename) 20 | except ValueError: 21 | # When the file is missing, let's forgive and ignore that. 22 | result = name 23 | return result 24 | -------------------------------------------------------------------------------- /src/charts/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "archery.fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "archery.name" . }} 7 | helm.sh/chart: {{ include "archery.chart" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.port }} 14 | targetPort: {{ .Values.service.targetPort }} 15 | protocol: TCP 16 | name: archery 17 | selector: 18 | app.kubernetes.io/name: {{ include "archery.name" . }} 19 | app.kubernetes.io/instance: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /src/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM hhyo/archery-base:1.2 2 | 3 | WORKDIR /opt/archery 4 | 5 | COPY . /opt/archery/ 6 | 7 | #archery 8 | RUN cd /opt \ 9 | && yum -y install nginx \ 10 | && source /opt/venv4archery/bin/activate \ 11 | && pip3 install -r /opt/archery/requirements.txt \ 12 | && cp /opt/archery/src/docker/nginx.conf /etc/nginx/ \ 13 | && cp /opt/archery/src/docker/supervisord.conf /etc/ \ 14 | && mv /opt/sqladvisor /opt/archery/src/plugins/ \ 15 | && mv /opt/soar /opt/archery/src/plugins/ \ 16 | && mv /opt/tmp_binlog2sql /opt/archery/src/plugins/binlog2sql 17 | 18 | #port 19 | EXPOSE 9123 20 | 21 | #start service 22 | ENTRYPOINT bash /opt/archery/src/docker/startup.sh && bash 23 | -------------------------------------------------------------------------------- /common/static/datetimepicker/js/bootstrap-datetimepicker.zh-CN.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simplified Chinese translation for bootstrap-datetimepicker 3 | * Yuan Cheung 4 | */ 5 | ;(function($){ 6 | $.fn.datetimepicker.dates['zh-CN'] = { 7 | days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"], 8 | daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"], 9 | daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"], 10 | months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], 11 | monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], 12 | today: "今天", 13 | suffix: [], 14 | meridiem: ["上午", "下午"] 15 | }; 16 | }(jQuery)); 17 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'Release v$NEXT_PATCH_VERSION' 2 | tag-template: 'v$NEXT_PATCH_VERSION' 3 | categories: 4 | - title: '变更说明' 5 | labels: 6 | - 'feature' 7 | - 'enhancement' 8 | - title: '修复说明' 9 | labels: 10 | - 'fix' 11 | - 'bugfix' 12 | - 'bug' 13 | - title: '易用性改进' 14 | labels: 15 | - 'ease of use' 16 | exclude-labels: 17 | - 'skip-changelog' 18 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 19 | template: | 20 | ## Release date: xxx年xx月xx日 21 | 22 | 代码变更: https://github.com/hhyo/Archery/compare/$PREVIOUS_TAG...v$NEXT_PATCH_VERSION 23 | 贡献者: $CONTRIBUTORS 24 | 25 | $CHANGES 26 | 27 | ## 升级步骤 28 | - https://github.com/hhyo/Archery/wiki/升级 29 | -------------------------------------------------------------------------------- /src/charts/charts/inception/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "inception.fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "inception.name" . }} 7 | helm.sh/chart: {{ include "inception.chart" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.port }} 14 | targetPort: {{ .Values.service.targetPort }} 15 | protocol: TCP 16 | name: inception 17 | selector: 18 | app.kubernetes.io/name: {{ include "inception.name" . }} 19 | app.kubernetes.io/instance: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /src/charts/charts/goinception/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "goinception.fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "goinception.name" . }} 7 | helm.sh/chart: {{ include "goinception.chart" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.port }} 14 | targetPort: {{ .Values.service.targetPort }} 15 | protocol: TCP 16 | name: goinception 17 | selector: 18 | app.kubernetes.io/name: {{ include "goinception.name" . }} 19 | app.kubernetes.io/instance: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /src/docker/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /opt/archery 4 | 5 | echo 切换python运行环境 6 | source /opt/venv4archery/bin/activate 7 | #pip install -r requirements.txt -i https://mirrors.ustc.edu.cn/pypi/web/simple/ 8 | 9 | echo 修改重定向端口 10 | if [[ -z $NGINX_PORT ]]; then 11 | sed -i "s/:nginx_port//g" /etc/nginx/nginx.conf 12 | else 13 | sed -i "s/nginx_port/$NGINX_PORT/g" /etc/nginx/nginx.conf 14 | fi 15 | 16 | echo 启动nginx 17 | /usr/sbin/nginx 18 | 19 | echo 收集所有的静态文件到STATIC_ROOT 20 | python3 manage.py collectstatic -v0 --noinput 21 | 22 | echo 启动Django Q cluster 23 | supervisord -c /etc/supervisord.conf 24 | 25 | echo 启动服务 26 | gunicorn -w 4 -k gevent -b 127.0.0.1:8888 --timeout 600 archery.wsgi:application 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /sql_api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.contrib.auth import get_user_model 3 | 4 | User = get_user_model() 5 | 6 | 7 | class InfoTest(TestCase): 8 | def setUp(self) -> None: 9 | self.superuser = User.objects.create(username='super', is_superuser=True) 10 | self.client.force_login(self.superuser) 11 | 12 | def tearDown(self) -> None: 13 | self.superuser.delete() 14 | 15 | def test_info_api(self): 16 | r = self.client.get('/api/info') 17 | r_json = r.json() 18 | self.assertIsInstance(r_json['archery']['version'], str) 19 | 20 | def test_debug_api(self): 21 | r = self.client.get('/api/debug') 22 | r_json = r.json() 23 | self.assertIsInstance(r_json['archery']['version'], str) 24 | -------------------------------------------------------------------------------- /src/init_sql/v1.7.0_v1.7.1.sql: -------------------------------------------------------------------------------- 1 | -- 增加资源组粒度的查询权限,v1.7.0的model中遗漏,全新安装的需要补充 2 | set @content_type_id=(select id from django_content_type where app_label='sql' and model='permission'); 3 | INSERT IGNORE INTO auth_permission (name, content_type_id, codename) VALUES ('可查询所在资源组内的所有实例', @content_type_id, 'query_resource_group_instance'); 4 | 5 | -- 记录企业微信userid 6 | alter table sql_users add wx_user_id varchar(64) default null comment '企业微信UserID'; 7 | 8 | -- 删除阿里云AK配置表,转移到系统配置中,扩大系统配置项长度 9 | drop table aliyun_access_key; 10 | alter table sql_config modify `item` varchar(200) NOT NULL comment '配置项', 11 | modify `value` varchar(500) NOT NULL DEFAULT '' comment '配置项值'; 12 | 13 | -- 使用django-mirage-field加密实例信息,扩大字段长度 14 | alter table sql_instance modify `user` varchar(200) DEFAULT NULL COMMENT '用户名'; 15 | -------------------------------------------------------------------------------- /common/middleware/check_login_middleware.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | import re 3 | from django.http import HttpResponseRedirect 4 | from django.utils.deprecation import MiddlewareMixin 5 | 6 | IGNORE_URL = [ 7 | '/login/', 8 | '/authenticate/', 9 | '/signup/', 10 | '/api/info' 11 | ] 12 | 13 | IGNORE_URL_RE = r'/admin/\w*' 14 | 15 | 16 | class CheckLoginMiddleware(MiddlewareMixin): 17 | @staticmethod 18 | def process_request(request): 19 | """ 20 | 该函数在每个函数之前检查是否登录,若未登录,则重定向到/login/ 21 | """ 22 | if not request.user.is_authenticated: 23 | # 以下是不用跳转到login页面的url白名单 24 | if request.path not in IGNORE_URL and re.match(IGNORE_URL_RE, request.path) is None: 25 | return HttpResponseRedirect('/login/') 26 | -------------------------------------------------------------------------------- /src/init_sql/v1.4.2_v1.4.3.sql: -------------------------------------------------------------------------------- 1 | -- 修改工单状态 2 | UPDATE sql_workflow SET status = 'workflow_finish' WHERE status='已正常结束'; 3 | UPDATE sql_workflow SET status = 'workflow_abort' WHERE status='人工终止流程'; 4 | UPDATE sql_workflow SET status = 'workflow_manreviewing' WHERE status='等待审核人审核'; 5 | UPDATE sql_workflow SET status = 'workflow_review_pass' WHERE status='审核通过'; 6 | UPDATE sql_workflow SET status = 'workflow_timingtask' WHERE status='定时执行'; 7 | UPDATE sql_workflow SET status = 'workflow_executing' WHERE status='执行中'; 8 | UPDATE sql_workflow SET status = 'workflow_autoreviewwrong' WHERE status='自动审核不通过'; 9 | UPDATE sql_workflow SET status = 'workflow_exception' WHERE status='执行有异常'; 10 | 11 | -- display修改为not null 12 | alter table sql_users modify display varchar(50) not null default '' comment '显示的中文名'; 13 | -------------------------------------------------------------------------------- /src/init_sql/v1.7.6_v1.7.7.sql: -------------------------------------------------------------------------------- 1 | -- 修改多对多的中间表 2 | alter table resource_group_user 3 | rename to sql_users_resource_group, 4 | drop create_time, 5 | change user_id users_id int(11) NOT NULL COMMENT '用户', 6 | change resource_group_id resourcegroup_id int(11) NOT NULL COMMENT '资源组'; 7 | 8 | alter table resource_group_instance 9 | rename to sql_instance_resource_group, 10 | drop create_time, 11 | change resource_group_id resourcegroup_id int(11) NOT NULL COMMENT '资源组'; 12 | 13 | alter table sql_instance_tag_relations 14 | rename to sql_instance_instance_tag, 15 | drop `active`, 16 | drop create_time, 17 | change instance_tag_id instancetag_id int(11) NOT NULL COMMENT '关联标签ID'; 18 | 19 | -- 实例配置表新增默认数据库字段 20 | ALTER TABLE sql_instance ADD `db_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '数据库'; 21 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 15 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 3 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - bug 10 | - enhancement 11 | # Label to use when marking an issue as stale 12 | staleLabel: wontfix 13 | # Comment to post when marking an issue as stale. Set to `false` to disable 14 | markComment: > 15 | 你已经很久没有回复这个issue了,如果没有进一步的信息的话, 会作为不活跃issue关闭, 感谢你对本项目的贡献。 16 | 17 | This issue has been automatically marked as stale because it has not had 18 | recent activity. It will be closed if no further activity occurs. Thank you 19 | for your contributions. 20 | # Comment to post when closing a stale issue. Set to `false` to disable 21 | closeComment: false 22 | -------------------------------------------------------------------------------- /common/static/sb-admin-2/js/sb-admin-2.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - SB Admin 2 v3.3.7+1 (http://startbootstrap.com/template-overviews/sb-admin-2) 3 | * Copyright 2013-2016 Start Bootstrap 4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE) 5 | */ 6 | $(function(){$("#side-menu").metisMenu()}),$(function(){$(window).bind("load resize",function(){var i=50,n=this.window.innerWidth>0?this.window.innerWidth:this.screen.width;n<768?($("div.navbar-collapse").addClass("collapse"),i=100):$("div.navbar-collapse").removeClass("collapse");var e=(this.window.innerHeight>0?this.window.innerHeight:this.screen.height)-1;e-=i,e<1&&(e=1),e>i&&$("#page-wrapper").css("min-height",e+"px")});for(var i=window.location,n=$("ul.nav a").filter(function(){return this.href==i}).addClass("active").parent();;){if(!n.is("li"))break;n=n.parent().addClass("in").parent()}}); -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: BUG提交 3 | about: BUG提交,必须使用这个模板,不规范issue将直接关闭 4 | title: "[ bug ]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 在提交 issue 前,请查阅以下资源,请先进行搜索来保证没有类似的 issue。 11 | [文档](https://github.com/hhyo/archery/wiki) | [FAQ](https://github.com/hhyo/archery/wiki/FAQ) 12 | 13 | ### 重现步骤 14 | 15 | 21 | 22 | ### 期待结果和实际结果 23 | 期待结果: 24 | 25 | 实际结果: 26 | 27 | ### 截图 28 | 29 | 30 | 32 | ### 错误日志 33 | 34 | 35 | 43 | ### 版本信息 44 | 应用版本/分支: 45 | 46 | 部署方式:Docker、手工部署 47 | 48 | 53 | -------------------------------------------------------------------------------- /src/docker-compose/inception/inc.cnf: -------------------------------------------------------------------------------- 1 | [inception] 2 | general_log=1 3 | general_log_file=inception.log 4 | port=6669 5 | socket=/tmp/inc.socket 6 | character-set-client-handshake=0 7 | character-set-server=utf8 8 | inception_language_code=zh-CN 9 | inception_remote_system_password=123456 10 | inception_remote_system_user=root 11 | inception_remote_backup_port=3306 12 | inception_remote_backup_host=mysql 13 | inception_support_charset=utf8,utf8mb4 14 | inception_enable_nullable=0 15 | inception_check_primary_key=1 16 | inception_check_column_comment=1 17 | inception_check_table_comment=1 18 | inception_osc_on=OFF 19 | inception_osc_bin_dir=/usr/bin 20 | inception_osc_min_table_size=10 21 | inception_osc_chunk_time=0.1 22 | inception_enable_blob_type=1 23 | inception_check_column_default_value=1 24 | 25 | inception_enable_select_star=ON 26 | inception_enable_identifer_keyword=ON 27 | inception_enable_autoincrement_unsigned=ON 28 | inception_check_identifier=OFF 29 | -------------------------------------------------------------------------------- /common/static/bootstrap-select/js/i18n/defaults-zh_CN.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap-select v1.13.9 (https://developer.snapappointments.com/bootstrap-select) 3 | * 4 | * Copyright 2012-2019 SnapAppointments, LLC 5 | * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) 6 | */ 7 | 8 | !function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"\u6ca1\u6709\u9009\u4e2d\u4efb\u4f55\u9879",noneResultsText:"\u6ca1\u6709\u627e\u5230\u5339\u914d\u9879",countSelectedText:"\u9009\u4e2d{1}\u4e2d\u7684{0}\u9879",maxOptionsText:["\u8d85\u51fa\u9650\u5236 (\u6700\u591a\u9009\u62e9{n}\u9879)","\u7ec4\u9009\u62e9\u8d85\u51fa\u9650\u5236(\u6700\u591a\u9009\u62e9{n}\u7ec4)"],multipleSeparator:", ",selectAllText:"\u5168\u9009",deselectAllText:"\u53d6\u6d88\u5168\u9009"}}); -------------------------------------------------------------------------------- /common/static/bootstrap-select/js/i18n/defaults-zh_CN.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../js/i18n/defaults-zh_CN.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AAChB,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AACjC,IAAI,gBAAgB,CAAC,CAAC,UAAU,CAAC;AACjC,IAAI,eAAe,CAAC,CAAC,UAAU,CAAC;AAChC,IAAI,iBAAiB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACtC,IAAI,cAAc,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC;AAC9D,IAAI,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AAC7B,IAAI,aAAa,CAAC,CAAC,KAAK,CAAC;AACzB,IAAI,eAAe,CAAC,CAAC,MAAM,CAAC;AAC5B,EAAE,EAAE,CAAC;AACL,GAAG,MAAM,EAAE,CAAC","file":"defaults-zh_CN.js","sourcesContent":["(function ($) {\r\n $.fn.selectpicker.defaults = {\r\n noneSelectedText: '没有选中任何项',\r\n noneResultsText: '没有找到匹配项',\r\n countSelectedText: '选中{1}中的{0}项',\r\n maxOptionsText: ['超出限制 (最多选择{n}项)', '组选择超出限制(最多选择{n}组)'],\r\n multipleSeparator: ', ',\r\n selectAllText: '全选',\r\n deselectAllText: '取消全选'\r\n };\r\n})(jQuery);\r\n"]} -------------------------------------------------------------------------------- /src/docker-compose/mysql/my.cnf: -------------------------------------------------------------------------------- 1 | [mysqld_safe] 2 | socket = /var/run/mysqld/mysqld.sock 3 | nice = 0 4 | 5 | [mysqld] 6 | pid-file = /var/run/mysqld/mysqld.pid 7 | socket = /var/run/mysqld/mysqld.sock 8 | port = 3306 9 | basedir = /usr 10 | datadir = /var/lib/mysql 11 | tmpdir = /tmp 12 | lc-messages-dir = /usr/share/mysql 13 | skip-external-locking 14 | lower_case_table_names=1 15 | default-time_zone = '+8:00' 16 | 17 | innodb_buffer_pool_size = 512M 18 | 19 | server-id = 100 20 | log_bin = /var/log/mysql/mysql-bin.log 21 | expire_logs_days = 1 22 | max_binlog_size = 500M 23 | 24 | character-set-server = utf8mb4 25 | collation-server = utf8mb4_general_ci 26 | 27 | slow_query_log_file = mysql-slow.log 28 | slow_query_log = 1 29 | long_query_time = 1 30 | 31 | [client] 32 | default-character-set=utf8mb4 33 | 34 | [mysqldump] 35 | quick 36 | quote-names 37 | max_allowed_packet = 1024M 38 | 39 | 40 | !includedir /etc/mysql/conf.d/ 41 | -------------------------------------------------------------------------------- /sql/templates/dbaprinciples.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |
7 |
8 | {% endblock content %} 9 | 10 | {% block js %} 11 | {% load static %} 12 | 13 | 31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /sql/plugins/sqladvisor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @author: hhyo 4 | @license: Apache Licence 5 | @file: sqladvisor.py 6 | @time: 2019/03/04 7 | """ 8 | __author__ = 'hhyo' 9 | 10 | from common.config import SysConfig 11 | from sql.plugins.plugin import Plugin 12 | 13 | 14 | class SQLAdvisor(Plugin): 15 | def __init__(self): 16 | self.path = SysConfig().get('sqladvisor') 17 | self.required_args = ['q'] 18 | self.disable_args = [] 19 | super(Plugin, self).__init__() 20 | 21 | def generate_args2cmd(self, args, shell): 22 | """ 23 | 转换请求参数为命令行 24 | :param args: 25 | :param shell: 26 | :return: 27 | """ 28 | if shell: 29 | cmd_args = self.path if self.path else '' 30 | for name, value in args.items(): 31 | cmd_args += f' -{name} "{value}"' 32 | else: 33 | cmd_args = [self.path] 34 | for name, value in args.items(): 35 | cmd_args.append(f'-{name}') 36 | cmd_args.append(f'{value}') 37 | return cmd_args 38 | -------------------------------------------------------------------------------- /common/utils/wx_api.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import logging 4 | import requests 5 | from common.config import SysConfig 6 | from django.core.cache import cache 7 | 8 | logger = logging.getLogger('default') 9 | 10 | 11 | def get_wx_access_token(): 12 | # 优先获取缓存 13 | try: 14 | token = cache.get('wx_access_token') 15 | except Exception as e: 16 | logger.error(f"获取企业微信token缓存出错:{e}") 17 | token = None 18 | if token: 19 | return token 20 | # 请求企业微信接口获取 21 | sys_config = SysConfig() 22 | corp_id = sys_config.get('wx_corpid') 23 | corp_secret = sys_config.get('wx_app_secret') 24 | url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corp_id}&corpsecret={corp_secret}" 25 | resp = requests.get(url, timeout=3).json() 26 | if resp.get('errcode') == 0: 27 | access_token = resp.get('access_token') 28 | expires_in = resp.get('expires_in') 29 | cache.set('wx_access_token', access_token, timeout=expires_in - 60) 30 | return access_token 31 | else: 32 | logger.error(f"获取企业微信access_token出错:{resp}") 33 | return None 34 | -------------------------------------------------------------------------------- /src/init_sql/del_permissions.sql: -------------------------------------------------------------------------------- 1 | -- 用于删除非自定义权限,如果不需要使用model权限管理,可以执行该脚本,仅保留自定义权限 2 | set @content_type_id=(select id from django_content_type where app_label='sql' and model='permission'); 3 | 4 | -- delete auth_group_permissions 5 | delete a 6 | from auth_group_permissions a 7 | join auth_permission b on a.permission_id = b.id 8 | where (b.content_type_id <> @content_type_id or 9 | (b.content_type_id = @content_type_id and 10 | codename in ('add_permission', 'change_permission', 'delete_permission'))); 11 | 12 | -- delete sql_users_user_permissions 13 | delete a 14 | from sql_users_user_permissions a 15 | join auth_permission b on a.permission_id = b.id 16 | where (b.content_type_id <> @content_type_id or 17 | (b.content_type_id = @content_type_id and 18 | codename in ('add_permission', 'change_permission', 'delete_permission'))); 19 | 20 | -- delete auth_permission 21 | delete 22 | from auth_permission 23 | where (content_type_id <> @content_type_id or 24 | (content_type_id = @content_type_id and 25 | codename in ('add_permission', 'change_permission', 'delete_permission'))); 26 | -------------------------------------------------------------------------------- /common/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @author: hhyo 4 | @license: Apache Licence 5 | @file: views.py 6 | @time: 2019/12/21 7 | """ 8 | from django.shortcuts import render 9 | 10 | __author__ = 'hhyo' 11 | 12 | from django.http import ( 13 | HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotFound, 14 | HttpResponseServerError, 15 | ) 16 | from django.views.decorators.csrf import requires_csrf_token 17 | 18 | 19 | @requires_csrf_token 20 | def bad_request(request, exception, template_name='errors/400.html'): 21 | return HttpResponseBadRequest(render(request, template_name)) 22 | 23 | 24 | @requires_csrf_token 25 | def permission_denied(request, exception, template_name='errors/403.html'): 26 | return HttpResponseForbidden(render(request, template_name)) 27 | 28 | 29 | @requires_csrf_token 30 | def page_not_found(request, exception, template_name='errors/404.html'): 31 | return HttpResponseNotFound(render(request, template_name)) 32 | 33 | 34 | @requires_csrf_token 35 | def server_error(request, template_name='errors/500.html'): 36 | return HttpResponseServerError(render(request, template_name)) 37 | -------------------------------------------------------------------------------- /common/static/bootstrap-table/export-libs/FileSaver/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright © 2016 [Eli Grey][1]. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | [1]: http://eligrey.com 12 | -------------------------------------------------------------------------------- /common/static/bootstrap-table/export-libs/html2canvas/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Niklas von Hertzen 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /common/static/bootstrap-table/export-libs/pdfmake/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 bpampuch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/charts/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "archery.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "archery.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "archery.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /src/plugins/soar.yaml: -------------------------------------------------------------------------------- 1 | # 是否允许测试环境与线上环境配置相同 2 | allow-online-as-test: false 3 | # 是否清理测试时产生的临时文件 4 | drop-test-temporary: true 5 | # 语法检查小工具 6 | only-syntax-check: false 7 | sampling-data-factor: 100 8 | sampling: true 9 | sampling-statistic-target: 100 10 | profiling: false 11 | trace: false 12 | # 日志级别,[0:Emergency, 1:Alert, 2:Critical, 3:Error, 4:Warning, 5:Notice, 6:Informational, 7:Debug] 13 | log-level: 7 14 | log-output: /opt/archery/logs/soar.log 15 | # 优化建议输出格式 16 | report-type: markdown 17 | ignore-rules: 18 | - "" 19 | # 启发式算法相关配置 20 | max-join-table-count: 5 21 | max-group-by-cols-count: 5 22 | max-distinct-count: 5 23 | max-index-cols-count: 5 24 | max-total-rows: 9999999 25 | spaghetti-query-length: 2048 26 | allow-drop-index: false 27 | # EXPLAIN相关配置 28 | explain-sql-report-type: pretty 29 | explain-type: extended 30 | explain-format: traditional 31 | explain-warn-select-type: 32 | - "" 33 | explain-warn-access-type: 34 | - ALL 35 | explain-max-keys: 3 36 | explain-min-keys: 0 37 | explain-max-rows: 10000 38 | explain-warn-extra: 39 | - "" 40 | explain-max-filtered: 100 41 | explain-warn-scalability: 42 | - O(n) 43 | query: "" 44 | list-heuristic-rules: false 45 | list-test-sqls: false 46 | verbose: true 47 | -------------------------------------------------------------------------------- /common/static/bootstrap-table/export-libs/es6-promise/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /common/static/bootstrap-table/export-libs/jsPDF/MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2016 James Hall, https://github.com/MrRio/jsPDF 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/charts/charts/inception/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "inception.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "inception.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "inception.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /src/docker-compose/archery/soar.yaml: -------------------------------------------------------------------------------- 1 | # 是否允许测试环境与线上环境配置相同 2 | allow-online-as-test: false 3 | # 是否清理测试时产生的临时文件 4 | drop-test-temporary: true 5 | # 语法检查小工具 6 | only-syntax-check: false 7 | sampling-data-factor: 100 8 | sampling: false 9 | sampling-statistic-target: 100 10 | profiling: false 11 | trace: false 12 | # 日志级别,[0:Emergency, 1:Alert, 2:Critical, 3:Error, 4:Warning, 5:Notice, 6:Informational, 7:Debug] 13 | log-level: 3 14 | log-output: /opt/archery/logs/soar.log 15 | # 优化建议输出格式 16 | report-type: markdown 17 | ignore-rules: 18 | - "" 19 | # 启发式算法相关配置 20 | max-join-table-count: 5 21 | max-group-by-cols-count: 5 22 | max-distinct-count: 5 23 | max-index-cols-count: 5 24 | max-total-rows: 9999999 25 | spaghetti-query-length: 2048 26 | allow-drop-index: false 27 | # EXPLAIN相关配置 28 | explain-sql-report-type: pretty 29 | explain-type: extended 30 | explain-format: traditional 31 | explain-warn-select-type: 32 | - "" 33 | explain-warn-access-type: 34 | - ALL 35 | explain-max-keys: 3 36 | explain-min-keys: 0 37 | explain-max-rows: 10000 38 | explain-warn-extra: 39 | - "" 40 | explain-max-filtered: 100 41 | explain-warn-scalability: 42 | - O(n) 43 | query: "" 44 | list-heuristic-rules: false 45 | list-test-sqls: false 46 | verbose: true 47 | -------------------------------------------------------------------------------- /src/charts/charts/goinception/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "goinception.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "goinception.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "goinception.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /common/static/bootstrap-table/export-libs/jsPDF-AutoTable/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Simon Bengtsson, https://github.com/someatoms/jspdf-autotable 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/init_sql/v1.0_v1.1.0.sql: -------------------------------------------------------------------------------- 1 | -- 表结构变更 2 | ALTER TABLE sql_group 3 | ADD ding_webhook VARCHAR(255) NOT NULL DEFAULT '' 4 | AFTER group_level; 5 | ALTER TABLE sql_workflow 6 | ADD engineer_display VARCHAR(50) NOT NULL DEFAULT '' 7 | AFTER engineer; 8 | ALTER TABLE workflow_audit 9 | ADD create_user_display VARCHAR(50) NOT NULL DEFAULT '' 10 | AFTER create_user; 11 | ALTER TABLE query_privileges_apply 12 | ADD user_display VARCHAR(50) NOT NULL DEFAULT '' 13 | AFTER user_name; 14 | ALTER TABLE query_privileges 15 | ADD user_display VARCHAR(50) NOT NULL DEFAULT '' 16 | AFTER user_name; 17 | ALTER TABLE query_log 18 | ADD user_display VARCHAR(50) NOT NULL DEFAULT '' 19 | AFTER username; 20 | 21 | -- 数据清洗 22 | UPDATE sql_workflow, sql_users 23 | SET engineer_display = display 24 | WHERE engineer = username; 25 | UPDATE workflow_audit, sql_users 26 | SET create_user_display = display 27 | WHERE create_user = username; 28 | UPDATE query_privileges_apply, sql_users 29 | SET user_display = display 30 | WHERE user_name = username; 31 | UPDATE query_privileges, sql_users 32 | SET user_display = display 33 | WHERE user_name = username; 34 | UPDATE query_log, sql_users 35 | SET user_display = display 36 | WHERE query_log.username = sql_users.username; -------------------------------------------------------------------------------- /src/init_sql/v1.2.0_v1.3.0.sql: -------------------------------------------------------------------------------- 1 | -- 增加工单日志表 2 | CREATE TABLE `workflow_log` ( 3 | `id` bigint(20) NOT NULL AUTO_INCREMENT, 4 | `audit_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '工单审批id', 5 | `operation_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '操作类型,0提交/待审核、1审核通过、2审核不通过、3审核取消/取消执行、4定时执行、5执行工单、6执行结束', 6 | `operation_type_desc` char(10) NOT NULL DEFAULT '' COMMENT '操作类型描述', 7 | `operation_info` varchar(200) NOT NULL DEFAULT '' COMMENT '操作信息', 8 | `operator` varchar(30) NOT NULL DEFAULT '' COMMENT '操作人', 9 | `operator_display` varchar(50) NOT NULL DEFAULT '' COMMENT '操作人中文名', 10 | `operation_time` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '操作时间', 11 | PRIMARY KEY (`id`), 12 | index idx_audit_id(audit_id) 13 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 14 | 15 | -- 增加菜单权限 16 | set @content_type_id=(select id from django_content_type where app_label='sql' and model='permission'); 17 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('菜单 数据库审核', @content_type_id, 'menu_themis'); 18 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('菜单 实例管理', @content_type_id, 'menu_instance'); 19 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('菜单 Binlog2SQL', @content_type_id, 'menu_binlog2sql'); 20 | -------------------------------------------------------------------------------- /src/charts/charts/goinception/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "goinception.fullname" . -}} 3 | {{- $ingressPaths := .Values.ingress.paths -}} 4 | apiVersion: extensions/v1beta1 5 | kind: Ingress 6 | metadata: 7 | name: {{ $fullName }} 8 | labels: 9 | app.kubernetes.io/name: {{ include "goinception.name" . }} 10 | helm.sh/chart: {{ include "goinception.chart" . }} 11 | app.kubernetes.io/instance: {{ .Release.Name }} 12 | app.kubernetes.io/managed-by: {{ .Release.Service }} 13 | {{- with .Values.ingress.annotations }} 14 | annotations: 15 | {{- toYaml . | nindent 4 }} 16 | {{- end }} 17 | spec: 18 | {{- if .Values.ingress.tls }} 19 | tls: 20 | {{- range .Values.ingress.tls }} 21 | - hosts: 22 | {{- range .hosts }} 23 | - {{ . | quote }} 24 | {{- end }} 25 | secretName: {{ .secretName }} 26 | {{- end }} 27 | {{- end }} 28 | rules: 29 | {{- range .Values.ingress.hosts }} 30 | - host: {{ . | quote }} 31 | http: 32 | paths: 33 | {{- range $ingressPaths }} 34 | - path: {{ . }} 35 | backend: 36 | serviceName: {{ $fullName }} 37 | servicePort: http 38 | {{- end }} 39 | {{- end }} 40 | {{- end }} 41 | -------------------------------------------------------------------------------- /src/charts/charts/inception/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "inception.fullname" . -}} 3 | {{- $ingressPaths := .Values.ingress.paths -}} 4 | apiVersion: extensions/v1beta1 5 | kind: Ingress 6 | metadata: 7 | name: {{ $fullName }} 8 | labels: 9 | app.kubernetes.io/name: {{ include "inception.name" . }} 10 | helm.sh/chart: {{ include "inception.chart" . }} 11 | app.kubernetes.io/instance: {{ .Release.Name }} 12 | app.kubernetes.io/managed-by: {{ .Release.Service }} 13 | {{- with .Values.ingress.annotations }} 14 | annotations: 15 | {{- toYaml . | nindent 4 }} 16 | {{- end }} 17 | spec: 18 | {{- if .Values.ingress.tls }} 19 | tls: 20 | {{- range .Values.ingress.tls }} 21 | - hosts: 22 | {{- range .hosts }} 23 | - {{ . | quote }} 24 | {{- end }} 25 | secretName: {{ .secretName }} 26 | {{- end }} 27 | {{- end }} 28 | rules: 29 | {{- range .Values.ingress.hosts }} 30 | - host: {{ . | quote }} 31 | http: 32 | paths: 33 | {{- range $ingressPaths }} 34 | - path: {{ . }} 35 | backend: 36 | serviceName: {{ $fullName }} 37 | servicePort: http 38 | {{- end }} 39 | {{- end }} 40 | {{- end }} 41 | -------------------------------------------------------------------------------- /common/static/ace/snippets/sql.js: -------------------------------------------------------------------------------- 1 | define("ace/snippets/sql",["require","exports","module"],function(e,t,n){"use strict";t.snippetText="snippet tbl\n create table ${1:table} (\n ${2:columns}\n );\nsnippet col\n ${1:name} ${2:type} ${3:default ''} ${4:not null}\nsnippet ccol\n ${1:name} varchar2(${2:size}) ${3:default ''} ${4:not null}\nsnippet ncol\n ${1:name} number ${3:default 0} ${4:not null}\nsnippet dcol\n ${1:name} date ${3:default sysdate} ${4:not null}\nsnippet ind\n create index ${3:$1_$2} on ${1:table}(${2:column});\nsnippet uind\n create unique index ${1:name} on ${2:table}(${3:column});\nsnippet tblcom\n comment on table ${1:table} is '${2:comment}';\nsnippet colcom\n comment on column ${1:table}.${2:column} is '${3:comment}';\nsnippet addcol\n alter table ${1:table} add (${2:column} ${3:type});\nsnippet seq\n create sequence ${1:name} start with ${2:1} increment by ${3:1} minvalue ${4:1};\nsnippet s*\n select * from ${1:table}\n",t.scope="sql"}); (function() { 2 | window.require(["ace/snippets/sql"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /common/utils/extend_json_encoder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | import simplejson as json 3 | 4 | from decimal import Decimal 5 | from datetime import datetime, date, timedelta 6 | from functools import singledispatch 7 | 8 | 9 | @singledispatch 10 | def convert(o): 11 | raise TypeError('can not convert type') 12 | 13 | 14 | @convert.register(datetime) 15 | def _(o): 16 | return o.strftime('%Y-%m-%d %H:%M:%S') 17 | 18 | 19 | @convert.register(date) 20 | def _(o): 21 | return o.strftime('%Y-%m-%d') 22 | 23 | 24 | @convert.register(timedelta) 25 | def _(o): 26 | return o.__str__() 27 | 28 | 29 | @convert.register(Decimal) 30 | def _(o): 31 | return float(o) 32 | 33 | 34 | class ExtendJSONEncoder(json.JSONEncoder): 35 | def default(self, obj): 36 | try: 37 | return convert(obj) 38 | except TypeError: 39 | return super(ExtendJSONEncoder, self).default(obj) 40 | 41 | 42 | class ExtendJSONEncoderFTime(json.JSONEncoder): 43 | 44 | def default(self, obj): 45 | try: 46 | if isinstance(obj, datetime): 47 | return obj.isoformat(' ') 48 | else: 49 | return convert(obj) 50 | except TypeError: 51 | return super(ExtendJSONEncoderFTime, self).default(obj) 52 | -------------------------------------------------------------------------------- /src/charts/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "archery.fullname" . -}} 3 | {{- $ingressPaths := .Values.ingress.paths -}} 4 | {{- $servicePort := .Values.ingress.servicePort -}} 5 | apiVersion: extensions/v1beta1 6 | kind: Ingress 7 | metadata: 8 | name: {{ $fullName }} 9 | labels: 10 | app.kubernetes.io/name: {{ include "archery.name" . }} 11 | helm.sh/chart: {{ include "archery.chart" . }} 12 | app.kubernetes.io/instance: {{ .Release.Name }} 13 | app.kubernetes.io/managed-by: {{ .Release.Service }} 14 | {{- with .Values.ingress.annotations }} 15 | annotations: 16 | {{- toYaml . | nindent 4 }} 17 | {{- end }} 18 | spec: 19 | {{- if .Values.ingress.tls }} 20 | tls: 21 | {{- range .Values.ingress.tls }} 22 | - hosts: 23 | {{- range .hosts }} 24 | - {{ . | quote }} 25 | {{- end }} 26 | secretName: {{ .secretName }} 27 | {{- end }} 28 | {{- end }} 29 | rules: 30 | {{- range .Values.ingress.hosts }} 31 | - host: {{ . | quote }} 32 | http: 33 | paths: 34 | {{- range $ingressPaths }} 35 | - path: {{ . }} 36 | backend: 37 | serviceName: {{ $fullName }} 38 | servicePort: {{ $servicePort }} 39 | {{- end }} 40 | {{- end }} 41 | {{- end }} 42 | -------------------------------------------------------------------------------- /common/static/bootstrap-select/js/i18n/defaults-zh_CN.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap-select v1.13.9 (https://developer.snapappointments.com/bootstrap-select) 3 | * 4 | * Copyright 2012-2019 SnapAppointments, LLC 5 | * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) 6 | */ 7 | 8 | (function (root, factory) { 9 | if (root === undefined && window !== undefined) root = window; 10 | if (typeof define === 'function' && define.amd) { 11 | // AMD. Register as an anonymous module unless amdModuleId is set 12 | define(["jquery"], function (a0) { 13 | return (factory(a0)); 14 | }); 15 | } else if (typeof module === 'object' && module.exports) { 16 | // Node. Does not work with strict CommonJS, but 17 | // only CommonJS-like environments that support module.exports, 18 | // like Node. 19 | module.exports = factory(require("jquery")); 20 | } else { 21 | factory(root["jQuery"]); 22 | } 23 | }(this, function (jQuery) { 24 | 25 | (function ($) { 26 | $.fn.selectpicker.defaults = { 27 | noneSelectedText: '没有选中任何项', 28 | noneResultsText: '没有找到匹配项', 29 | countSelectedText: '选中{1}中的{0}项', 30 | maxOptionsText: ['超出限制 (最多选择{n}项)', '组选择超出限制(最多选择{n}组)'], 31 | multipleSeparator: ', ', 32 | selectAllText: '全选', 33 | deselectAllText: '取消全选' 34 | }; 35 | })(jQuery); 36 | 37 | 38 | })); 39 | //# sourceMappingURL=defaults-zh_CN.js.map -------------------------------------------------------------------------------- /sql/locale/zh_Hans/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | # 更改翻译文件后务必使用 python manage.py compilemessages 编译翻译文件 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: PACKAGE VERSION\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2019-02-14 19:34+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "Language: \n" 17 | "MIME-Version: 1.0\n" 18 | "Content-Type: text/plain; charset=UTF-8\n" 19 | "Content-Transfer-Encoding: 8bit\n" 20 | 21 | #: .\sql\models.py:111 22 | msgid "workflow_finish" 23 | msgstr "已正常结束" 24 | 25 | #: .\sql\models.py:112 26 | msgid "workflow_abort" 27 | msgstr "人工终止流程" 28 | 29 | #: .\sql\models.py:113 30 | msgid "workflow_manreviewing" 31 | msgstr "等待审核人审核" 32 | 33 | #: .\sql\models.py:114 34 | msgid "workflow_review_pass" 35 | msgstr "审核通过" 36 | 37 | #: .\sql\models.py:115 38 | msgid "workflow_timingtask" 39 | msgstr "定时执行" 40 | 41 | #: .\sql\models.py:116 42 | msgid "workflow_executing" 43 | msgstr "执行中" 44 | 45 | #: .\sql\models.py:117 46 | msgid "workflow_autoreviewwrong" 47 | msgstr "自动审核不通过" 48 | 49 | #: .\sql\models.py:118 50 | msgid "workflow_exception" 51 | msgstr "执行有异常" 52 | -------------------------------------------------------------------------------- /sql/locale/zh_Hans/LC_MESSAGES/djangojs.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | # 更改翻译文件后务必使用 python manage.py compilemessages 编译翻译文件 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: PACKAGE VERSION\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2019-02-14 19:34+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "Language: \n" 17 | "MIME-Version: 1.0\n" 18 | "Content-Type: text/plain; charset=UTF-8\n" 19 | "Content-Transfer-Encoding: 8bit\n" 20 | 21 | #: .\sql\models.py:111 22 | msgid "workflow_finish" 23 | msgstr "已正常结束" 24 | 25 | #: .\sql\models.py:112 26 | msgid "workflow_abort" 27 | msgstr "人工终止流程" 28 | 29 | #: .\sql\models.py:113 30 | msgid "workflow_manreviewing" 31 | msgstr "等待审核人审核" 32 | 33 | #: .\sql\models.py:114 34 | msgid "workflow_review_pass" 35 | msgstr "审核通过" 36 | 37 | #: .\sql\models.py:115 38 | msgid "workflow_timingtask" 39 | msgstr "定时执行" 40 | 41 | #: .\sql\models.py:116 42 | msgid "workflow_executing" 43 | msgstr "执行中" 44 | 45 | #: .\sql\models.py:117 46 | msgid "workflow_autoreviewwrong" 47 | msgstr "自动审核不通过" 48 | 49 | #: .\sql\models.py:118 50 | msgid "workflow_exception" 51 | msgstr "执行有异常" 52 | -------------------------------------------------------------------------------- /sql/locale/en/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | # 更改翻译文件后务必使用 python manage.py compilemessages 编译翻译文件 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: PACKAGE VERSION\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2019-02-14 19:47+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "Language: \n" 17 | "MIME-Version: 1.0\n" 18 | "Content-Type: text/plain; charset=UTF-8\n" 19 | "Content-Transfer-Encoding: 8bit\n" 20 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 21 | #: .\sql\models.py:111 22 | msgid "workflow_finish" 23 | msgstr "Workflow finished" 24 | 25 | #: .\sql\models.py:112 26 | msgid "workflow_abort" 27 | msgstr "Aborted" 28 | 29 | #: .\sql\models.py:113 30 | msgid "workflow_manreviewing" 31 | msgstr "Reviewing" 32 | 33 | #: .\sql\models.py:114 34 | msgid "workflow_review_pass" 35 | msgstr "Review passed" 36 | 37 | #: .\sql\models.py:115 38 | msgid "workflow_timingtask" 39 | msgstr "Scheduled" 40 | 41 | #: .\sql\models.py:116 42 | msgid "workflow_executing" 43 | msgstr "Executing" 44 | 45 | #: .\sql\models.py:117 46 | msgid "workflow_autoreviewwrong" 47 | msgstr "Rejected" 48 | 49 | #: .\sql\models.py:118 50 | msgid "workflow_exception" 51 | msgstr "Exceptions during execution" 52 | -------------------------------------------------------------------------------- /sql/locale/en/LC_MESSAGES/djangojs.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | # 更改翻译文件后务必使用 python manage.py compilemessages 编译翻译文件 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: PACKAGE VERSION\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2019-02-14 19:47+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "Language: \n" 17 | "MIME-Version: 1.0\n" 18 | "Content-Type: text/plain; charset=UTF-8\n" 19 | "Content-Transfer-Encoding: 8bit\n" 20 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 21 | #: .\sql\models.py:111 22 | msgid "workflow_finish" 23 | msgstr "Workflow finished" 24 | 25 | #: .\sql\models.py:112 26 | msgid "workflow_abort" 27 | msgstr "Aborted" 28 | 29 | #: .\sql\models.py:113 30 | msgid "workflow_manreviewing" 31 | msgstr "Reviewing" 32 | 33 | #: .\sql\models.py:114 34 | msgid "workflow_review_pass" 35 | msgstr "Review passed" 36 | 37 | #: .\sql\models.py:115 38 | msgid "workflow_timingtask" 39 | msgstr "Scheduled" 40 | 41 | #: .\sql\models.py:116 42 | msgid "workflow_executing" 43 | msgstr "Executing" 44 | 45 | #: .\sql\models.py:117 46 | msgid "workflow_autoreviewwrong" 47 | msgstr "Rejected" 48 | 49 | #: .\sql\models.py:118 50 | msgid "workflow_exception" 51 | msgstr "Exceptions during execution" 52 | -------------------------------------------------------------------------------- /src/script/analysis_slow_query.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR="$( cd "$( dirname "$0" )" && pwd )" 3 | cd ${DIR} 4 | 5 | #配置Archery数据库的连接地址 6 | archery_db_host="127.0.0.1" 7 | archery_db_port=3306 8 | archery_db_user="root" 9 | archery_db_password="123456" 10 | archery_db_database="archery" 11 | 12 | #被分析实例的慢日志位置,建议定期清理日志文件,否则会影响分析效率 13 | slowquery_file="/home/mysql/log_slow.log" 14 | 15 | #pt-query-digest可执行文件路径 16 | pt_query_digest="/usr/bin/pt-query-digest" 17 | 18 | #被分析实例的连接信息 19 | hostname="mysql_host:mysql_port" # 需要和Archery实例配置中的内容保持一致,用于筛选,配置错误会导致数据无法展示 20 | 21 | #获取上次分析时间,初始化时请删除last_analysis_time_$hostname文件,可分析全部日志数据 22 | if [[ -s last_analysis_time_${hostname} ]]; then 23 | last_analysis_time=`cat last_analysis_time_${hostname}` 24 | else 25 | last_analysis_time='1000-01-01 00:00:00' 26 | fi 27 | 28 | #收集日志 29 | #RDS需要增加--no-version-check选项 30 | ${pt_query_digest} \ 31 | --user=${archery_db_user} --password=${archery_db_password} --port=${archery_db_port} \ 32 | --review h=${archery_db_host},D=${archery_db_database},t=mysql_slow_query_review \ 33 | --history h=${archery_db_host},D=${archery_db_database},t=mysql_slow_query_review_history \ 34 | --no-report --limit=100% --charset=utf8 \ 35 | --since "$last_analysis_time" \ 36 | --filter="\$event->{Bytes} = length(\$event->{arg}) and \$event->{hostname}=\"$hostname\" and \$event->{client}=\$event->{ip} " \ 37 | ${slowquery_file} > /tmp/analysis_slow_query.log 38 | 39 | if [[ $? -ne 0 ]]; then 40 | echo "failed" 41 | else 42 | echo `date +"%Y-%m-%d %H:%M:%S"`>last_analysis_time_${hostname} 43 | fi 44 | -------------------------------------------------------------------------------- /common/utils/permission.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | import simplejson as json 3 | from django.shortcuts import render 4 | from django.http import HttpResponse 5 | 6 | 7 | # 管理员操作权限验证 8 | def superuser_required(func): 9 | def wrapper(request, *args, **kw): 10 | # 获取用户信息,权限验证 11 | user = request.user 12 | 13 | if user.is_superuser is False: 14 | if request.is_ajax(): 15 | result = {'status': 1, 'msg': '您无权操作,请联系管理员', 'data': []} 16 | return HttpResponse(json.dumps(result), content_type='application/json') 17 | else: 18 | context = {'errMsg': "您无权操作,请联系管理员"} 19 | return render(request, "error.html", context) 20 | 21 | return func(request, *args, **kw) 22 | 23 | return wrapper 24 | 25 | 26 | # 角色操作权限验证 27 | def role_required(roles=()): 28 | def _deco(func): 29 | def wrapper(request, *args, **kw): 30 | # 获取用户信息,权限验证 31 | user = request.user 32 | if user.role not in roles and user.is_superuser is False: 33 | if request.is_ajax(): 34 | result = {'status': 1, 'msg': '您无权操作,请联系管理员', 'data': []} 35 | return HttpResponse(json.dumps(result), content_type='application/json') 36 | else: 37 | context = {'errMsg': "您无权操作,请联系管理员"} 38 | return render(request, "error.html", context) 39 | 40 | return func(request, *args, **kw) 41 | 42 | return wrapper 43 | 44 | return _deco 45 | -------------------------------------------------------------------------------- /common/static/bootstrap-table/js/bootstrap-table-zh-CN.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bootstrap-table - An extended Bootstrap table with radio, checkbox, sort, pagination, and other added features. (supports twitter bootstrap v2 and v3). 3 | * 4 | * @version v1.14.1 5 | * @homepage https://bootstrap-table.com 6 | * @author wenzhixin (http://wenzhixin.net.cn/) 7 | * @license MIT 8 | */ 9 | 10 | (function(a,b){if('function'==typeof define&&define.amd)define([],b);else if('undefined'!=typeof exports)b();else{b(),a.bootstrapTableZhCN={exports:{}}.exports}})(this,function(){'use strict';(function(a){a.fn.bootstrapTable.locales['zh-CN']={formatLoadingMessage:function(){return'\u6B63\u5728\u52AA\u529B\u5730\u52A0\u8F7D\u6570\u636E\u4E2D\uFF0C\u8BF7\u7A0D\u5019\u2026\u2026'},formatRecordsPerPage:function(a){return'\u6BCF\u9875\u663E\u793A '+a+' \u6761\u8BB0\u5F55'},formatShowingRows:function(a,b,c){return'\u663E\u793A\u7B2C '+a+' \u5230\u7B2C '+b+' \u6761\u8BB0\u5F55\uFF0C\u603B\u5171 '+c+' \u6761\u8BB0\u5F55'},formatSearch:function(){return'\u641C\u7D22'},formatNoMatches:function(){return'\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u8BB0\u5F55'},formatPaginationSwitch:function(){return'\u9690\u85CF/\u663E\u793A\u5206\u9875'},formatRefresh:function(){return'\u5237\u65B0'},formatToggle:function(){return'\u5207\u6362'},formatColumns:function(){return'\u5217'},formatExport:function(){return'\u5BFC\u51FA\u6570\u636E'},formatClearFilters:function(){return'\u6E05\u7A7A\u8FC7\u6EE4'}},a.extend(a.fn.bootstrapTable.defaults,a.fn.bootstrapTable.locales['zh-CN'])})(jQuery)}); -------------------------------------------------------------------------------- /src/charts/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range $.Values.ingress.paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}{{ . }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "archery.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get svc -w {{ include "archery.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "archery.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "archery.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | echo "Visit http://127.0.0.1:9123 to use your application" 20 | kubectl port-forward $POD_NAME 9123:9123 21 | {{- end }} 22 | -------------------------------------------------------------------------------- /common/utils/aes_decryptor.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import AES 2 | from binascii import b2a_hex, a2b_hex 3 | 4 | 5 | class Prpcrypt(): 6 | def __init__(self): 7 | self.key = 'eCcGFZQj6PNoSSma31LR39rTzTbLkU8E'.encode('utf-8') 8 | self.mode = AES.MODE_CBC 9 | 10 | # 加密函数,如果text不足16位就用空格补足为16位, 11 | # 如果大于16当时不是16的倍数,那就补足为16的倍数。 12 | def encrypt(self, text): 13 | cryptor = AES.new(self.key, self.mode, b'0000000000000000') 14 | # 这里密钥key 长度必须为16(AES-128), 15 | # 24(AES-192),或者32 (AES-256)Bytes 长度 16 | # 目前AES-128 足够目前使用 17 | length = 16 18 | count = len(text) 19 | if count < length: 20 | add = (length - count) 21 | # \0 backspace 22 | text = text + ('\0' * add) 23 | elif count > length: 24 | add = (length - (count % length)) 25 | text = text + ('\0' * add) 26 | self.ciphertext = cryptor.encrypt(text.encode('utf-8')) 27 | # 因为AES加密时候得到的字符串不一定是ascii字符集的,输出到终端或者保存时候可能存在问题 28 | # 所以这里统一把加密后的字符串转化为16进制字符串 29 | return b2a_hex(self.ciphertext).decode(encoding='utf-8') 30 | 31 | # 解密后,去掉补足的空格用strip() 去掉 32 | def decrypt(self, text): 33 | cryptor = AES.new(self.key, self.mode, b'0000000000000000') 34 | plain_text = cryptor.decrypt(a2b_hex(text)) 35 | return plain_text.decode().rstrip('\0') 36 | 37 | 38 | if __name__ == '__main__': 39 | pc = Prpcrypt() # 初始化密钥 40 | e = pc.encrypt('123456') # 加密 41 | d = pc.decrypt(e) # 解密 42 | print("加密:", str(e)) 43 | print("解密:", str(d)) 44 | -------------------------------------------------------------------------------- /src/charts/charts/inception/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range $.Values.ingress.paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}{{ . }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "inception.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get svc -w {{ include "inception.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "inception.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "inception.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | echo "Visit http://127.0.0.1:8080 to use your application" 20 | kubectl port-forward $POD_NAME 8080:80 21 | {{- end }} 22 | -------------------------------------------------------------------------------- /src/charts/charts/goinception/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range $.Values.ingress.paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}{{ . }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "goinception.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get svc -w {{ include "goinception.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "goinception.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "goinception.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | echo "Visit http://127.0.0.1:8080 to use your application" 20 | kubectl port-forward $POD_NAME 8080:80 21 | {{- end }} 22 | -------------------------------------------------------------------------------- /sql/utils/tasks.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from django_q.tasks import schedule 3 | from django_q.models import Schedule 4 | 5 | import logging 6 | 7 | logger = logging.getLogger('default') 8 | 9 | 10 | def add_sql_schedule(name, run_date, workflow_id): 11 | """添加/修改sql定时任务""" 12 | del_schedule(name) 13 | schedule('sql.utils.execute_sql.execute', workflow_id, 14 | hook='sql.utils.execute_sql.execute_callback', 15 | name=name, schedule_type='O', next_run=run_date, repeats=1, timeout=-1) 16 | logger.debug(f"添加SQL定时执行任务:{name} 执行时间:{run_date}") 17 | 18 | 19 | def add_kill_conn_schedule(name, run_date, instance_id, thread_id): 20 | """添加/修改终止数据库连接的定时任务""" 21 | del_schedule(name) 22 | schedule('sql.query.kill_query_conn', instance_id, thread_id, 23 | name=name, schedule_type='O', next_run=run_date, repeats=1, timeout=-1) 24 | 25 | 26 | def add_sync_ding_user_schedule(): 27 | """添加钉钉同步用户定时任务""" 28 | del_schedule(name='同步钉钉用户ID') 29 | schedule('common.utils.ding_api.sync_ding_user_id', 30 | name='同步钉钉用户ID', schedule_type='D', repeats=-1, timeout=-1) 31 | 32 | 33 | def del_schedule(name): 34 | """删除task""" 35 | try: 36 | sql_schedule = Schedule.objects.get(name=name) 37 | Schedule.delete(sql_schedule) 38 | # logger.debug(f'删除task:{name}') 39 | except Schedule.DoesNotExist: 40 | pass 41 | 42 | 43 | def task_info(name): 44 | """获取定时任务详情""" 45 | try: 46 | sql_schedule = Schedule.objects.get(name=name) 47 | return sql_schedule 48 | except Schedule.DoesNotExist: 49 | pass 50 | -------------------------------------------------------------------------------- /src/docker-compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | redis: 5 | image: redis:5 6 | container_name: redis 7 | restart: always 8 | command: redis-server --requirepass 123456 9 | expose: 10 | - "6379" 11 | 12 | mysql: 13 | image: mysql:5.7 14 | container_name: mysql 15 | restart: always 16 | ports: 17 | - "3306:3306" 18 | volumes: 19 | - "./mysql/my.cnf:/etc/mysql/my.cnf" 20 | - "./mysql/datadir:/var/lib/mysql" 21 | environment: 22 | MYSQL_DATABASE: archery 23 | MYSQL_ROOT_PASSWORD: 123456 24 | 25 | inception: 26 | image: hhyo/inception 27 | container_name: inception 28 | restart: always 29 | expose: 30 | - "6669" 31 | volumes: 32 | - "./inception/inc.cnf:/etc/inc.cnf" 33 | 34 | goinception: 35 | image: hanchuanchuan/goinception 36 | container_name: goinception 37 | restart: always 38 | expose: 39 | - "4000" 40 | volumes: 41 | - "./inception/config.toml:/etc/config.toml" 42 | 43 | archery: 44 | image: hhyo/archery:1.7.7 45 | container_name: archery 46 | restart: always 47 | ports: 48 | - "9123:9123" 49 | volumes: 50 | - "./archery/settings.py:/opt/archery/archery/settings.py" 51 | - "./archery/soar.yaml:/etc/soar.yaml" 52 | - "./archery/docs.md:/opt/archery/docs/docs.md" 53 | - "./archery/downloads:/opt/archery/downloads" 54 | - "./archery/sql/migrations:/opt/archery/sql/migrations" 55 | - "./archery/logs:/opt/archery/logs" 56 | entrypoint: "dockerize -wait tcp://mysql:3306 -wait tcp://redis:6379 -timeout 60s /opt/archery/src/docker/startup.sh" 57 | environment: 58 | NGINX_PORT: 9123 59 | -------------------------------------------------------------------------------- /sql/plugins/schemasync.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @author: hhyo 4 | @license: Apache Licence 5 | @file: schemasync.py 6 | @time: 2019/03/05 7 | """ 8 | __author__ = 'hhyo' 9 | 10 | from common.config import SysConfig 11 | from sql.plugins.plugin import Plugin 12 | 13 | 14 | class SchemaSync(Plugin): 15 | 16 | def __init__(self): 17 | self.path = 'schemasync' 18 | self.required_args = [] 19 | self.disable_args = [] 20 | super(Plugin, self).__init__() 21 | 22 | def generate_args2cmd(self, args, shell): 23 | """ 24 | 转换请求参数为命令行 25 | :param args: 26 | :param shell: 27 | :return: 28 | """ 29 | k_options = ['sync-auto-inc', 'sync-comments'] 30 | kv_options = ['tag', 'output-directory', 'log-directory'] 31 | v_options = ['source', 'target'] 32 | if shell: 33 | cmd_args = self.path if self.path else '' 34 | for name, value in args.items(): 35 | if name in k_options and value: 36 | cmd_args += f' --{name}' 37 | elif name in kv_options: 38 | cmd_args += f' --{name}={value}' 39 | elif name in v_options: 40 | cmd_args += f' {value}' 41 | else: 42 | cmd_args = [self.path] 43 | for name, value in args.items(): 44 | if name in k_options and value: 45 | cmd_args.append(f'--{name}') 46 | elif name in kv_options: 47 | cmd_args.append(f'--{name}') 48 | cmd_args.append(f'{value}') 49 | elif name in ['source', 'target']: 50 | cmd_args.append(f'{value}') 51 | return cmd_args 52 | -------------------------------------------------------------------------------- /sql/plugins/pt_archiver.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @author: hhyo 4 | @license: Apache Licence 5 | @file: pt_archiver.py 6 | @time: 2020/01/10 7 | """ 8 | from common.config import SysConfig 9 | from sql.plugins.plugin import Plugin 10 | 11 | __author__ = 'hhyo' 12 | 13 | 14 | class PtArchiver(Plugin): 15 | """ 16 | pt-archiver归档数据 17 | """ 18 | 19 | def __init__(self): 20 | self.path = 'pt-archiver' 21 | self.required_args = [] 22 | self.disable_args = ['analyze'] 23 | super(Plugin, self).__init__() 24 | 25 | def generate_args2cmd(self, args, shell): 26 | """ 27 | 转换请求参数为命令行 28 | :param args: 29 | :param shell: 30 | :return: 31 | """ 32 | k_options = ['no-version-check', 'statistics', 'bulk-insert', 'bulk-delete', 'purge', 'no-delete'] 33 | kv_options = ['source', 'dest', 'file', 'where', 'progress', 'charset', 'limit', 'txn-size', 'sleep'] 34 | if shell: 35 | cmd_args = self.path if self.path else '' 36 | for name, value in args.items(): 37 | if name in k_options and value: 38 | cmd_args += f' --{name}' 39 | elif name in kv_options: 40 | if name == 'where': 41 | cmd_args += f' --{name} "{value}"' 42 | else: 43 | cmd_args += f' --{name} {value}' 44 | else: 45 | cmd_args = [self.path] 46 | for name, value in args.items(): 47 | if name in k_options and value: 48 | cmd_args.append(f'--{name}') 49 | elif name in kv_options: 50 | cmd_args.append(f'--{name}') 51 | cmd_args.append(f'{value}') 52 | return cmd_args 53 | -------------------------------------------------------------------------------- /common/static/sb-admin-2/js/sb-admin-2.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - SB Admin 2 v3.3.7+1 (http://startbootstrap.com/template-overviews/sb-admin-2) 3 | * Copyright 2013-2016 Start Bootstrap 4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE) 5 | */ 6 | $(function() { 7 | $('#side-menu').metisMenu(); 8 | }); 9 | 10 | //Loads the correct sidebar on window load, 11 | //collapses the sidebar on window resize. 12 | // Sets the min-height of #page-wrapper to window size 13 | $(function() { 14 | $(window).bind("load resize", function() { 15 | var topOffset = 50; 16 | var width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width; 17 | if (width < 768) { 18 | $('div.navbar-collapse').addClass('collapse'); 19 | topOffset = 100; // 2-row-menu 20 | } else { 21 | $('div.navbar-collapse').removeClass('collapse'); 22 | } 23 | 24 | var height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1; 25 | height = height - topOffset; 26 | if (height < 1) height = 1; 27 | if (height > topOffset) { 28 | $("#page-wrapper").css("min-height", (height) + "px"); 29 | } 30 | }); 31 | 32 | var url = window.location; 33 | // var element = $('ul.nav a').filter(function() { 34 | // return this.href == url; 35 | // }).addClass('active').parent().parent().addClass('in').parent(); 36 | var element = $('ul.nav a').filter(function() { 37 | return this.href == url; 38 | }).addClass('active').parent(); 39 | 40 | while (true) { 41 | if (element.is('li')) { 42 | element = element.parent().addClass('in').parent(); 43 | } else { 44 | break; 45 | } 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /sql/utils/resource_group.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | 3 | from sql.models import Users, Instance, ResourceGroup 4 | 5 | 6 | def user_groups(user): 7 | """ 8 | 获取用户关联资源组列表 9 | :param user: 10 | :return: 11 | """ 12 | if user.is_superuser: 13 | group_list = [group for group in ResourceGroup.objects.filter(is_deleted=0)] 14 | else: 15 | group_list = [group for group in Users.objects.get(id=user.id).resource_group.filter(is_deleted=0)] 16 | return group_list 17 | 18 | 19 | def user_instances(user, type=None, db_type=None, tag_codes=None): 20 | """ 21 | 获取用户实例列表(通过资源组间接关联) 22 | :param user: 23 | :param type: 实例类型 all:全部,master主库,salve从库 24 | :param db_type: 数据库类型, ['mysql','mssql'] 25 | :param tag_codes: 标签code列表, ['can_write', 'can_read'] 26 | :return: 27 | """ 28 | # 拥有所有实例权限的用户 29 | if user.has_perm('sql.query_all_instances'): 30 | instances = Instance.objects.all() 31 | else: 32 | # 先获取用户关联的资源组 33 | resource_groups = ResourceGroup.objects.filter(users=user, is_deleted=0) 34 | # 再获取实例 35 | instances = Instance.objects.filter(resource_group__in=resource_groups) 36 | # 过滤type 37 | if type: 38 | instances = instances.filter(type=type) 39 | 40 | # 过滤db_type 41 | if db_type: 42 | instances = instances.filter(db_type__in=db_type) 43 | 44 | # 过滤tag 45 | if tag_codes: 46 | for tag_code in tag_codes: 47 | instances = instances.filter(instance_tag__tag_code=tag_code, instance_tag__active=True) 48 | return instances.distinct() 49 | 50 | 51 | def auth_group_users(auth_group_names, group_id): 52 | """ 53 | 获取资源组内关联指定权限组的用户 54 | :param auth_group_names: 权限组名称list 55 | :param group_id: 资源组ID 56 | :return: 57 | """ 58 | # 获取资源组关联的用户 59 | users = ResourceGroup.objects.get(group_id=group_id).users_set.all() 60 | # 过滤在该权限组中的用户 61 | users = users.filter(groups__name__in=auth_group_names) 62 | return users 63 | -------------------------------------------------------------------------------- /src/charts/charts/inception/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "inception.fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "inception.name" . }} 7 | helm.sh/chart: {{ include "inception.chart" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: {{ .Release.Service }} 10 | spec: 11 | replicas: {{ .Values.replicaCount }} 12 | selector: 13 | matchLabels: 14 | app.kubernetes.io/name: {{ include "inception.name" . }} 15 | app.kubernetes.io/instance: {{ .Release.Name }} 16 | template: 17 | metadata: 18 | labels: 19 | app.kubernetes.io/name: {{ include "inception.name" . }} 20 | app.kubernetes.io/instance: {{ .Release.Name }} 21 | spec: 22 | containers: 23 | - name: {{ .Chart.Name }} 24 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 25 | imagePullPolicy: {{ .Values.image.pullPolicy }} 26 | ports: 27 | - name: inception 28 | containerPort: 6669 29 | protocol: TCP 30 | livenessProbe: 31 | tcpSocket: 32 | port: inception 33 | resources: 34 | {{- toYaml .Values.resources | nindent 12 }} 35 | {{- with .Values.volumeMounts }} 36 | volumeMounts: 37 | - name: inception-config-volume 38 | subPath: inc.cnf 39 | mountPath: /etc/inc.cnf 40 | {{- end }} 41 | {{- with .Values.volumes }} 42 | volumes: 43 | {{- toYaml . | nindent 8 }} 44 | {{- end }} 45 | {{- with .Values.nodeSelector }} 46 | nodeSelector: 47 | {{- toYaml . | nindent 8 }} 48 | {{- end }} 49 | {{- with .Values.affinity }} 50 | affinity: 51 | {{- toYaml . | nindent 8 }} 52 | {{- end }} 53 | {{- with .Values.tolerations }} 54 | tolerations: 55 | {{- toYaml . | nindent 8 }} 56 | {{- end }} 57 | -------------------------------------------------------------------------------- /src/charts/charts/goinception/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "goinception.fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "goinception.name" . }} 7 | helm.sh/chart: {{ include "goinception.chart" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: {{ .Release.Service }} 10 | spec: 11 | replicas: {{ .Values.replicaCount }} 12 | selector: 13 | matchLabels: 14 | app.kubernetes.io/name: {{ include "goinception.name" . }} 15 | app.kubernetes.io/instance: {{ .Release.Name }} 16 | template: 17 | metadata: 18 | labels: 19 | app.kubernetes.io/name: {{ include "goinception.name" . }} 20 | app.kubernetes.io/instance: {{ .Release.Name }} 21 | spec: 22 | containers: 23 | - name: {{ .Chart.Name }} 24 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 25 | imagePullPolicy: {{ .Values.image.pullPolicy }} 26 | ports: 27 | - name: goinception 28 | containerPort: 4000 29 | protocol: TCP 30 | livenessProbe: 31 | tcpSocket: 32 | port: goinception 33 | resources: 34 | {{- toYaml .Values.resources | nindent 12 }} 35 | {{- with .Values.volumeMounts }} 36 | volumeMounts: 37 | - name: goinception-config-volume 38 | subPath: config.toml 39 | mountPath: /etc/config.toml 40 | {{- end }} 41 | {{- with .Values.volumes }} 42 | volumes: 43 | {{- toYaml . | nindent 8 }} 44 | {{- end }} 45 | {{- with .Values.nodeSelector }} 46 | nodeSelector: 47 | {{- toYaml . | nindent 8 }} 48 | {{- end }} 49 | {{- with .Values.affinity }} 50 | affinity: 51 | {{- toYaml . | nindent 8 }} 52 | {{- end }} 53 | {{- with .Values.tolerations }} 54 | tolerations: 55 | {{- toYaml . | nindent 8 }} 56 | {{- end }} 57 | -------------------------------------------------------------------------------- /sql/completer/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @author: hhyo 4 | @license: Apache Licence 5 | @file: completion_engines.py 6 | @time: 2019/03/09 7 | """ 8 | 9 | __author__ = 'hhyo' 10 | 11 | 12 | class Completer: 13 | @property 14 | def name(self): 15 | """返回engine名称""" 16 | return 'Completer engine' 17 | 18 | @property 19 | def info(self): 20 | """返回引擎简介""" 21 | return 'Base Completer engine' 22 | 23 | def _get_sql_execute(self): 24 | """ 25 | 初始化数据库执行连接 26 | :return: 27 | """ 28 | 29 | def refresh_completions(self, reset=False): 30 | """ 31 | 刷新completer对象元数据 32 | :param reset: 33 | :return: 34 | """ 35 | 36 | def _on_completions_refreshed(self, new_completer): 37 | """ 38 | 刷新completer对象回调函数,替换对象 39 | :param new_completer: 40 | :return: 41 | """ 42 | 43 | def get_completions(self, text, cursor_position): 44 | """ 45 | 获取补全提示 46 | :param text: 47 | :param cursor_position: 48 | :return: 49 | """ 50 | 51 | @staticmethod 52 | def convert2ace_js(completions): 53 | """ 54 | 转换completions为ace.js所需要的补全列表格式[{"caption":,"meta":,"name":,"value":,"score":] 55 | caption :字幕,也就是展示在列表中的内容 56 | meta :展示类型 57 | name :名称 58 | value :值 59 | score :分数,越大的排在越上面 60 | :return: 61 | """ 62 | ace_completions = [] 63 | for completion in completions: 64 | comp = dict() 65 | comp["caption"] = completion.display 66 | comp["meta"] = completion.display_meta 67 | comp["name"] = '' 68 | comp["value"] = '' 69 | comp["score"] = completion.start_position 70 | ace_completions.append(comp) 71 | return ace_completions 72 | 73 | 74 | def get_comp_engine(instance=None, db_name=None): 75 | """获取SQL补全engine""" 76 | if instance.db_type == 'mysql': 77 | from .mysql import MysqlComEngine 78 | return MysqlComEngine(instance=instance, db_name=db_name) 79 | -------------------------------------------------------------------------------- /common/static/ace/mode-sql.js: -------------------------------------------------------------------------------- 1 | define("ace/mode/sql_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){var e="select|insert|update|delete|from|where|and|or|group|by|order|limit|offset|having|as|case|when|then|else|end|type|left|right|join|on|outer|desc|asc|union|create|table|primary|key|if|foreign|not|references|default|null|inner|cross|natural|database|drop|grant",t="true|false",n="avg|count|first|last|max|min|sum|ucase|lcase|mid|len|round|rank|now|format|coalesce|ifnull|isnull|nvl",r="int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp|money|real|number|integer",i=this.createKeywordMapper({"support.function":n,keyword:e,"constant.language":t,"storage.type":r},"identifier",!0);this.$rules={start:[{token:"comment",regex:"--.*$"},{token:"comment",start:"/\\*",end:"\\*/"},{token:"string",regex:'".*?"'},{token:"string",regex:"'.*?'"},{token:"string",regex:"`.*?`"},{token:"constant.numeric",regex:"[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"},{token:i,regex:"[a-zA-Z_$][a-zA-Z0-9_$]*\\b"},{token:"keyword.operator",regex:"\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|="},{token:"paren.lparen",regex:"[\\(]"},{token:"paren.rparen",regex:"[\\)]"},{token:"text",regex:"\\s+"}]},this.normalizeRules()};r.inherits(s,i),t.SqlHighlightRules=s}),define("ace/mode/sql",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/sql_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./sql_highlight_rules").SqlHighlightRules,o=function(){this.HighlightRules=s,this.$behaviour=this.$defaultBehaviour};r.inherits(o,i),function(){this.lineCommentStart="--",this.$id="ace/mode/sql"}.call(o.prototype),t.Mode=o}); (function() { 2 | window.require(["ace/mode/sql"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /common/static/metisMenu/css/metisMenu.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * metismenu - v2.7.8 3 | * A jQuery menu plugin 4 | * https://github.com/onokumus/metismenu#readme 5 | * 6 | * Made by Osman Nuri Okumus (https://github.com/onokumus) 7 | * Under MIT License 8 | */.metismenu .arrow{float:right;line-height:1.42857}[dir=rtl] .metismenu .arrow{float:left}.metismenu .glyphicon.arrow:before{content:"\e079"}.metismenu .active>a>.glyphicon.arrow:before{content:"\e114"}.metismenu .fa.arrow:before{content:"\f104"}.metismenu .active>a>.fa.arrow:before{content:"\f107"}.metismenu .ion.arrow:before{content:"\f3d2"}.metismenu .active>a>.ion.arrow:before{content:"\f3d0"}.metismenu .plus-times{float:right}[dir=rtl] .metismenu .plus-times{float:left}.metismenu .fa.plus-times:before{content:"\f067"}.metismenu .active>a>.fa.plus-times{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.metismenu .plus-minus{float:right}[dir=rtl] .metismenu .plus-minus{float:left}.metismenu .fa.plus-minus:before{content:"\f067"}.metismenu .active>a>.fa.plus-minus:before{content:"\f068"}.metismenu .collapse{display:none}.metismenu .collapse.in{display:block}.metismenu .collapsing{height:0;overflow:hidden;position:relative;transition-duration:.35s;transition-property:height,visibility;transition-timing-function:ease}.metismenu .has-arrow{position:relative}.metismenu .has-arrow:after{-webkit-transform:rotate(-45deg) translateY(-50%);-webkit-transform-origin:top;border-color:initial;border-style:solid;border-width:1px 0 0 1px;content:"";height:.5em;position:absolute;right:1em;top:50%;transform:rotate(-45deg) translateY(-50%);transform-origin:top;transition:all .3s ease-out;width:.5em}[dir=rtl] .metismenu .has-arrow:after{-webkit-transform:rotate(135deg) translateY(-50%);left:1em;right:auto;transform:rotate(135deg) translateY(-50%)}.metismenu .active>.has-arrow:after,.metismenu .has-arrow[aria-expanded=true]:after{-webkit-transform:rotate(-135deg) translateY(-50%);transform:rotate(-135deg) translateY(-50%)}[dir=rtl] .metismenu .active>.has-arrow:after,[dir=rtl] .metismenu .has-arrow[aria-expanded=true]:after{-webkit-transform:rotate(225deg) translateY(-50%);transform:rotate(225deg) translateY(-50%)} 9 | /*# sourceMappingURL=metisMenu.min.css.map */ 10 | -------------------------------------------------------------------------------- /sql/plugins/plugin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @author: hhyo 4 | @license: Apache Licence 5 | @file: plugin.py 6 | @time: 2019/03/04 7 | """ 8 | __author__ = 'hhyo' 9 | 10 | import logging 11 | import subprocess 12 | import traceback 13 | 14 | logger = logging.getLogger('default') 15 | 16 | 17 | class Plugin: 18 | def __init__(self, path): 19 | self.path = path 20 | self.required_args = [] # 必须参数 21 | self.disable_args = [] # 禁用参数 22 | 23 | def check_args(self, args): 24 | """ 25 | 检查请求参数列表 26 | :return: {'status': 0, 'msg': 'ok', 'data': {}} 27 | """ 28 | args_check_result = {'status': 0, 'msg': 'ok', 'data': {}} 29 | # 检查路径 30 | if self.path is None: 31 | return {'status': 1, 'msg': '可执行文件路径不能为空!', 'data': {}} 32 | # 检查禁用参数 33 | for arg in args.keys(): 34 | if arg in self.disable_args: 35 | return {'status': 1, 'msg': '{arg}参数已被禁用'.format(arg=arg), 'data': {}} 36 | # 检查必须参数 37 | for req_arg in self.required_args: 38 | if req_arg not in args.keys(): 39 | return {'status': 1, 'msg': '必须指定{arg}参数'.format(arg=req_arg), 'data': {}} 40 | elif args[req_arg] is None or args[req_arg] == '': 41 | return {'status': 1, 'msg': '{arg}参数值不能为空'.format(arg=req_arg), 'data': {}} 42 | return args_check_result 43 | 44 | def generate_args2cmd(self, args, shell): 45 | """ 46 | 将请求参数转换为命令行参数 47 | :return: 48 | """ 49 | 50 | @staticmethod 51 | def execute_cmd(cmd_args, shell): 52 | """ 53 | 执行命令并且返回process 54 | :return: 55 | """ 56 | try: 57 | p = subprocess.Popen(cmd_args, 58 | shell=shell, 59 | stdout=subprocess.PIPE, 60 | stderr=subprocess.PIPE, 61 | universal_newlines=True) 62 | return p 63 | except Exception as e: 64 | logger.error("命令执行失败\n{}".format(traceback.format_exc())) 65 | raise RuntimeError('命令执行失败,失败原因:%s' % str(e)) 66 | -------------------------------------------------------------------------------- /common/static/bootstrap-table/export-libs/FileSaver/FileSaver.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | @source http://purl.eligrey.com/github/FileSaver.js/blob/master/src/FileSaver.js 3 | */ 4 | var module$FileSaver={},saveAs$$module$FileSaver=saveAs$$module$FileSaver||function(b){if(!("undefined"===typeof b||"undefined"!==typeof navigator&&/MSIE [1-9]\./.test(navigator.userAgent))){var f=b.document.createElementNS("http://www.w3.org/1999/xhtml","a"),q="download"in f,r=/constructor/i.test(b.HTMLElement)||b.safari,h=/CriOS\/[\d]+/.test(navigator.userAgent),k=b.setImmediate||b.setTimeout,t=function(a){k(function(){throw a;},0)},l=function(a){setTimeout(function(){"string"===typeof a?(b.URL|| 5 | b.webkitURL||b).revokeObjectURL(a):a.remove()},4E4)},m=function(a){return/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob([String.fromCharCode(65279),a],{type:a.type}):a},p=function(a,c,u){u||(a=m(a));var d=this,n="application/octet-stream"===a.type,g=function(){var a=["writestart","progress","write","writeend"];a=[].concat(a);for(var b=a.length;b--;){var c=d["on"+a[b]];if("function"===typeof c)try{c.call(d,d)}catch(v){t(v)}}};d.readyState=d.INIT; 6 | if(q){var e=(b.URL||b.webkitURL||b).createObjectURL(a);k(function(){f.href=e;f.download=c;var a=new MouseEvent("click");f.dispatchEvent(a);g();l(e);d.readyState=d.DONE},0)}else(function(){if((h||n&&r)&&b.FileReader){var c=new FileReader;c.onloadend=function(){var a=h?c.result:c.result.replace(/^data:[^;]*;/,"data:attachment/file;");b.open(a,"_blank")||(b.location.href=a);d.readyState=d.DONE;g()};c.readAsDataURL(a);d.readyState=d.INIT}else e||(e=(b.URL||b.webkitURL||b).createObjectURL(a)),n?b.location.href= 7 | e:b.open(e,"_blank")||(b.location.href=e),d.readyState=d.DONE,g(),l(e)})()},c=p.prototype;if("undefined"!==typeof navigator&&navigator.msSaveOrOpenBlob)return function(a,b,c){b=b||a.name||"download";c||(a=m(a));return navigator.msSaveOrOpenBlob(a,b)};c.abort=function(){};c.readyState=c.INIT=0;c.WRITING=1;c.DONE=2;c.error=c.onwritestart=c.onprogress=c.onwrite=c.onabort=c.onerror=c.onwriteend=null;return function(a,b,c){return new p(a,b||a.name||"download",c)}}}("undefined"!==typeof self&&self||"undefined"!== 8 | typeof window&&window||this);module$FileSaver.saveAs=saveAs$$module$FileSaver; 9 | -------------------------------------------------------------------------------- /common/utils/const.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | 3 | class Const(object): 4 | # 定时任务id的前缀 5 | workflowJobprefix = { 6 | 'query': 'query', 7 | 'sqlreview': 'sqlreview', 8 | 'archive': 'archive' 9 | } 10 | 11 | 12 | class WorkflowDict: 13 | # 工作流申请类型,1.query,2.SQL上线申请 14 | workflow_type = { 15 | 'query': 1, 16 | 'query_display': '查询权限申请', 17 | 'sqlreview': 2, 18 | 'sqlreview_display': 'SQL上线申请', 19 | 'archive': 3, 20 | 'archive_display': '数据归档申请', 21 | } 22 | 23 | # 工作流状态,0.待审核 1.审核通过 2.审核不通过 3.审核取消 24 | workflow_status = { 25 | 'audit_wait': 0, 26 | 'audit_wait_display': '待审核', 27 | 'audit_success': 1, 28 | 'audit_success_display': '审核通过', 29 | 'audit_reject': 2, 30 | 'audit_reject_display': '审核不通过', 31 | 'audit_abort': 3, 32 | 'audit_abort_display': '审核取消', 33 | } 34 | 35 | 36 | class SQLTuning: 37 | SYS_PARM_FILTER = [ 38 | 'BINLOG_CACHE_SIZE', 39 | 'BULK_INSERT_BUFFER_SIZE', 40 | 'HAVE_PARTITION_ENGINE', 41 | 'HAVE_QUERY_CACHE', 42 | 'INTERACTIVE_TIMEOUT', 43 | 'JOIN_BUFFER_SIZE', 44 | 'KEY_BUFFER_SIZE', 45 | 'KEY_CACHE_AGE_THRESHOLD', 46 | 'KEY_CACHE_BLOCK_SIZE', 47 | 'KEY_CACHE_DIVISION_LIMIT', 48 | 'LARGE_PAGES', 49 | 'LOCKED_IN_MEMORY', 50 | 'LONG_QUERY_TIME', 51 | 'MAX_ALLOWED_PACKET', 52 | 'MAX_BINLOG_CACHE_SIZE', 53 | 'MAX_BINLOG_SIZE', 54 | 'MAX_CONNECT_ERRORS', 55 | 'MAX_CONNECTIONS', 56 | 'MAX_JOIN_SIZE', 57 | 'MAX_LENGTH_FOR_SORT_DATA', 58 | 'MAX_SEEKS_FOR_KEY', 59 | 'MAX_SORT_LENGTH', 60 | 'MAX_TMP_TABLES', 61 | 'MAX_USER_CONNECTIONS', 62 | 'OPTIMIZER_PRUNE_LEVEL', 63 | 'OPTIMIZER_SEARCH_DEPTH', 64 | 'QUERY_CACHE_SIZE', 65 | 'QUERY_CACHE_TYPE', 66 | 'QUERY_PREALLOC_SIZE', 67 | 'RANGE_ALLOC_BLOCK_SIZE', 68 | 'READ_BUFFER_SIZE', 69 | 'READ_RND_BUFFER_SIZE', 70 | 'SORT_BUFFER_SIZE', 71 | 'SQL_MODE', 72 | 'TABLE_CACHE', 73 | 'THREAD_CACHE_SIZE', 74 | 'TMP_TABLE_SIZE', 75 | 'WAIT_TIMEOUT' 76 | ] 77 | -------------------------------------------------------------------------------- /src/init_sql/v1.6.1_v1.6.2.sql: -------------------------------------------------------------------------------- 1 | -- 资源组和用户关联表 2 | CREATE TABLE `resource_group_user` ( 3 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 4 | `resource_group_id` int(11) NOT NULL COMMENT '资源组', 5 | `user_id` int(11) NOT NULL COMMENT '用户', 6 | `create_time` datetime(6) NOT NULL COMMENT '创建时间', 7 | PRIMARY KEY (`id`), 8 | KEY `idx_user_id` (`user_id`), 9 | UNIQUE uniq_resource_group_id_instance_id(`resource_group_id`,`user_id`), 10 | CONSTRAINT `fk_resource_group_user_resource_group` FOREIGN KEY (`resource_group_id`) REFERENCES `resource_group` (`group_id`), 11 | CONSTRAINT `fk_resource_group_user` FOREIGN KEY (`user_id`) REFERENCES `sql_users` (`id`) 12 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 13 | 14 | -- 资源组和实例关联表 15 | CREATE TABLE `resource_group_instance` ( 16 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 17 | `resource_group_id` int(11) NOT NULL COMMENT '资源组', 18 | `instance_id` int(11) NOT NULL COMMENT '实例', 19 | `create_time` datetime(6) NOT NULL COMMENT '创建时间', 20 | PRIMARY KEY (`id`), 21 | KEY `idx_instance_id` (`instance_id`), 22 | UNIQUE uniq_resource_group_id_instance_id(`resource_group_id`,`instance_id`), 23 | CONSTRAINT `fk_resource_group_instance_resource_group` FOREIGN KEY (`resource_group_id`) REFERENCES `resource_group` (`group_id`), 24 | CONSTRAINT `fk_resource_group_instance` FOREIGN KEY (`instance_id`) REFERENCES `sql_instance` (`id`) 25 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 26 | 27 | -- 数据清洗 28 | set foreign_key_checks = 0; 29 | -- 用户关系数据 30 | insert into resource_group_user (resource_group_id, user_id, create_time) 31 | select group_id,object_id,create_time from resource_group_relations where object_type=0; 32 | -- 实例关系数据 33 | insert into resource_group_instance (resource_group_id, instance_id, create_time) 34 | select group_id,object_id,create_time from resource_group_relations where object_type=1; 35 | set foreign_key_checks = 1; 36 | 37 | -- 删除旧表 38 | drop table resource_group_relations; 39 | 40 | -- SQL上线工单增加可执行时间选择 41 | ALTER TABLE sql_workflow 42 | ADD run_date_start datetime(6) DEFAULT NULL COMMENT '可执行起始时间', 43 | ADD run_date_end datetime(6) DEFAULT NULL COMMENT '可执行结束时间'; 44 | 45 | -- 实例配置增加默认字符集信息 46 | ALTER TABLE sql_instance 47 | ADD `charset` varchar(20) DEFAULT NULL COMMENT '字符集' after `password`; 48 | -------------------------------------------------------------------------------- /sql/completer/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @author: hhyo 4 | @license: Apache Licence 5 | @file: tests.py 6 | @time: 2019/03/11 7 | """ 8 | from django.conf import settings 9 | from django.test import TestCase 10 | from prompt_toolkit.completion import Completion 11 | from sql.completer import get_comp_engine 12 | 13 | from sql.models import Instance 14 | 15 | __author__ = 'hhyo' 16 | 17 | 18 | class TestCompleter(TestCase): 19 | @classmethod 20 | def setUpClass(cls): 21 | """ 22 | 初始化补全引擎 23 | :return: 24 | """ 25 | # 使用 travis.ci 时实例和测试service保持一致 26 | cls.master = Instance(instance_name='test_instance', type='master', db_type='mysql', 27 | host=settings.DATABASES['default']['HOST'], 28 | port=settings.DATABASES['default']['PORT'], 29 | user=settings.DATABASES['default']['USER'], 30 | password=settings.DATABASES['default']['PASSWORD']) 31 | cls.master.save() 32 | cls.comp_engine = get_comp_engine(instance=cls.master, db_name=settings.DATABASES['default']['TEST']['NAME']) 33 | # 等待completion_refresher刷新完成 34 | while cls.comp_engine.completion_refresher.is_refreshing(): 35 | import time 36 | time.sleep(1) 37 | 38 | @classmethod 39 | def tearDownClass(cls): 40 | """ 41 | :return: 42 | """ 43 | cls.master.delete() 44 | cls.comp_engine.refresh_completions(reset=True) 45 | 46 | def test_table_names_after_from(self): 47 | text = 'SELECT * FROM ' 48 | position = len('SELECT * FROM ') 49 | self.comp_engine.get_completions(text=text, cursor_position=position) 50 | 51 | def test_suggested_column_names(self): 52 | text = 'SELECT FROM sql_users' 53 | position = len('SELECT ') 54 | self.comp_engine.get_completions(text=text, cursor_position=position) 55 | 56 | def test_function_name_completion(self): 57 | text = 'SELECT MA' 58 | position = len('SELECT MA') 59 | result = self.comp_engine.get_completions(text=text, cursor_position=position) 60 | self.comp_engine.convert2ace_js(result) 61 | self.assertListEqual(result, [Completion(text='MAX', start_position=-2), 62 | Completion(text='MASTER', start_position=-2)]) 63 | -------------------------------------------------------------------------------- /sql/plugins/binglog2sql.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @author: hhyo 4 | @license: Apache Licence 5 | @file: binglog2sql.py 6 | @time: 2019/03/23 7 | """ 8 | from common.config import SysConfig 9 | from sql.plugins.plugin import Plugin 10 | 11 | __author__ = 'hhyo' 12 | 13 | 14 | class Binlog2Sql(Plugin): 15 | 16 | def __init__(self): 17 | self.path = SysConfig().get('binlog2sql') 18 | self.required_args = [] 19 | self.disable_args = [] 20 | super(Plugin, self).__init__() 21 | 22 | def generate_args2cmd(self, args, shell): 23 | """ 24 | 转换请求参数为命令行 25 | :param args: 26 | :param shell: 27 | :return: 28 | """ 29 | conn_options = ['conn_options'] 30 | parse_mode_options = ['stop-never', 'no-primary-key', 'flashback'] 31 | range_options = ['back-interval', 'start-file', 'start-position', 'stop-file', 'stop-position', 32 | 'start-datetime', 'stop-datetime'] 33 | filter_options = ['databases', 'tables', 'only-dml', 'sql-type'] 34 | if shell: 35 | cmd_args = f'python {self.path}' if self.path else '' 36 | for name, value in args.items(): 37 | if name in conn_options: 38 | cmd_args += f' {value}' 39 | elif name in parse_mode_options and value: 40 | cmd_args += f' --{name}' 41 | elif name in range_options and value: 42 | cmd_args += f" --{name}='{value}'" 43 | elif name in filter_options and value: 44 | if name == 'only-dml': 45 | cmd_args += f' --{name}' 46 | else: 47 | cmd_args += f' --{name} {value}' 48 | else: 49 | cmd_args = [self.path] 50 | for name, value in args.items(): 51 | if name in conn_options: 52 | cmd_args.append(f'{value}') 53 | elif name in parse_mode_options: 54 | cmd_args.append(f'--{name}') 55 | elif name in range_options: 56 | cmd_args.append(f'--{name}') 57 | cmd_args.append(f'{value}') 58 | elif name in filter_options: 59 | cmd_args.append(f'--{name}') 60 | cmd_args.append(f'{value}') 61 | return cmd_args 62 | -------------------------------------------------------------------------------- /src/charts/charts/inception/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for inception. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: hhyo/inception 9 | tag: latest 10 | pullPolicy: IfNotPresent 11 | 12 | nameOverride: "" 13 | fullnameOverride: "" 14 | 15 | service: 16 | type: ClusterIP 17 | port: 6669 18 | targetPort: 6669 19 | 20 | ingress: 21 | enabled: false 22 | annotations: {} 23 | # kubernetes.io/ingress.class: nginx 24 | # kubernetes.io/tls-acme: "true" 25 | paths: [] 26 | hosts: 27 | - chart-example.local 28 | tls: [] 29 | # - secretName: chart-example-tls 30 | # hosts: 31 | # - chart-example.local 32 | 33 | configMap: 34 | enabled: true 35 | data: 36 | inc.cnf: |- 37 | [inception] 38 | general_log=1 39 | general_log_file=inception.log 40 | port=6669 41 | socket=/tmp/inc.socket 42 | character-set-client-handshake=0 43 | character-set-server=utf8mb4 44 | inception_language_code=zh-CN 45 | inception_remote_system_password=archery 46 | inception_remote_system_user=root 47 | inception_remote_backup_port=3306 48 | inception_remote_backup_host=archery-mysql 49 | inception_support_charset=utf8,utf8mb4 50 | inception_enable_nullable=0 51 | inception_check_primary_key=1 52 | inception_check_column_comment=1 53 | inception_check_table_comment=1 54 | inception_osc_on=OFF 55 | inception_osc_bin_dir=/usr/bin 56 | inception_osc_min_table_size=10 57 | inception_osc_chunk_time=0.1 58 | inception_enable_blob_type=1 59 | inception_check_column_default_value=1 60 | 61 | resources: {} 62 | # We usually recommend not to specify default resources and to leave this as a conscious 63 | # choice for the user. This also increases chances charts run on environments with little 64 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 65 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 66 | # limits: 67 | # cpu: 100m 68 | # memory: 128Mi 69 | # requests: 70 | # cpu: 100m 71 | # memory: 128Mi 72 | 73 | nodeSelector: {} 74 | 75 | tolerations: [] 76 | 77 | affinity: {} 78 | 79 | volumes: 80 | - name: inception-config-volume 81 | configMap: 82 | name: inception-config 83 | -------------------------------------------------------------------------------- /common/static/ace/snippets/sqlserver.js: -------------------------------------------------------------------------------- 1 | define("ace/snippets/sqlserver",["require","exports","module"],function(e,t,n){"use strict";t.snippetText="# ISNULL\nsnippet isnull\n ISNULL(${1:check_expression}, ${2:replacement_value})\n# FORMAT\nsnippet format\n FORMAT(${1:value}, ${2:format})\n# CAST\nsnippet cast\n CAST(${1:expression} AS ${2:data_type})\n# CONVERT\nsnippet convert\n CONVERT(${1:data_type}, ${2:expression})\n# DATEPART\nsnippet datepart\n DATEPART(${1:datepart}, ${2:date})\n# DATEDIFF\nsnippet datediff\n DATEDIFF(${1:datepart}, ${2:startdate}, ${3:enddate})\n# DATEADD\nsnippet dateadd\n DATEADD(${1:datepart}, ${2:number}, ${3:date})\n# DATEFROMPARTS \nsnippet datefromparts\n DATEFROMPARTS(${1:year}, ${2:month}, ${3:day})\n# OBJECT_DEFINITION\nsnippet objectdef\n SELECT OBJECT_DEFINITION(OBJECT_ID('${1:sys.server_permissions /*object name*/}'))\n# STUFF XML\nsnippet stuffxml\n STUFF((SELECT ', ' + ${1:ColumnName}\n FROM ${2:TableName}\n WHERE ${3:WhereClause}\n FOR XML PATH('')), 1, 1, '') AS ${4:Alias}\n ${5:/*https://msdn.microsoft.com/en-us/library/ms188043.aspx*/}\n# Create Procedure\nsnippet createproc\n -- =============================================\n -- Author: ${1:Author}\n -- Create date: ${2:Date}\n -- Description: ${3:Description}\n -- =============================================\n CREATE PROCEDURE ${4:Procedure_Name}\n ${5:/*Add the parameters for the stored procedure here*/}\n AS\n BEGIN\n -- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.\n SET NOCOUNT ON;\n \n ${6:/*Add the T-SQL statements to compute the return value here*/}\n \n END\n GO\n# Create Scalar Function\nsnippet createfn\n -- =============================================\n -- Author: ${1:Author}\n -- Create date: ${2:Date}\n -- Description: ${3:Description}\n -- =============================================\n CREATE FUNCTION ${4:Scalar_Function_Name}\n -- Add the parameters for the function here\n RETURNS ${5:Function_Data_Type}\n AS\n BEGIN\n DECLARE @Result ${5:Function_Data_Type}\n \n ${6:/*Add the T-SQL statements to compute the return value here*/}\n \n END\n GO",t.scope="sqlserver"}); (function() { 2 | window.require(["ace/snippets/sqlserver"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /common/static/ace/theme-github.js: -------------------------------------------------------------------------------- 1 | define("ace/theme/github",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-github",t.cssText='.ace-github .ace_gutter {background: #e8e8e8;color: #AAA;}.ace-github {background: #fff;color: #000;}.ace-github .ace_keyword {font-weight: bold;}.ace-github .ace_string {color: #D14;}.ace-github .ace_variable.ace_class {color: teal;}.ace-github .ace_constant.ace_numeric {color: #099;}.ace-github .ace_constant.ace_buildin {color: #0086B3;}.ace-github .ace_support.ace_function {color: #0086B3;}.ace-github .ace_comment {color: #998;font-style: italic;}.ace-github .ace_variable.ace_language {color: #0086B3;}.ace-github .ace_paren {font-weight: bold;}.ace-github .ace_boolean {font-weight: bold;}.ace-github .ace_string.ace_regexp {color: #009926;font-weight: normal;}.ace-github .ace_variable.ace_instance {color: teal;}.ace-github .ace_constant.ace_language {font-weight: bold;}.ace-github .ace_cursor {color: black;}.ace-github.ace_focus .ace_marker-layer .ace_active-line {background: rgb(255, 255, 204);}.ace-github .ace_marker-layer .ace_active-line {background: rgb(245, 245, 245);}.ace-github .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-github.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;}.ace-github.ace_nobold .ace_line > span {font-weight: normal !important;}.ace-github .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-github .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-github .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-github .ace_gutter-active-line {background-color : rgba(0, 0, 0, 0.07);}.ace-github .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-github .ace_invisible {color: #BFBFBF}.ace-github .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-github .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}); (function() { 2 | window.require(["ace/theme/github"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /src/init_sql/v1.6.7_v1.7.0.sql: -------------------------------------------------------------------------------- 1 | -- 删除Themis权限 2 | set @perm_id=(select id from auth_permission where codename='menu_themis'); 3 | delete from auth_group_permissions where permission_id=@perm_id; 4 | delete from sql_users_user_permissions where permission_id=@perm_id; 5 | delete from auth_permission where codename='menu_themis'; 6 | set @content_type_id=(select id from django_content_type where app_label='sql' and model='permission'); 7 | -- 增加实例账号管理权限,变更菜单权限信息 8 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('菜单 管理实例账号', @content_type_id, 'instance_account_manage'); 9 | UPDATE auth_permission set name='菜单 实例账号管理',codename='menu_instance_account' where codename='menu_instance_user'; 10 | -- 增加实例数据库权限 11 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('菜单 数据库管理', @content_type_id, 'menu_database'); 12 | -- 增加资源组粒度的查询权限 13 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('可查询所在资源组内的所有实例', @content_type_id, 'query_resource_group_instance'); 14 | -- 增加工具插件的权限 15 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('菜单 工具插件', @content_type_id, 'menu_menu_tools'); 16 | 17 | -- 添加钉钉user id 18 | alter table sql_users 19 | add ding_user_id varchar(64) default null comment '钉钉user_id'; 20 | 21 | 22 | -- 增加实例账号表 23 | CREATE TABLE `instance_account` ( 24 | `id` int(11) NOT NULL AUTO_INCREMENT, 25 | `user` varchar(128) NOT NULL COMMENT '账号', 26 | `host` varchar(64) NOT NULL COMMENT '主机', 27 | `password` varchar(128) NOT NULL COMMENT '密码', 28 | `remark` varchar(255) NOT NULL COMMENT '备注', 29 | `sys_time` datetime(6) NOT NULL COMMENT '系统时间', 30 | `instance_id` int(11) NOT NULL COMMENT '实例', 31 | PRIMARY KEY (`id`), 32 | UNIQUE KEY `uniq_instance_id_user_host` (`instance_id`,`user`,`host`), 33 | CONSTRAINT `fk_account_sql_instance_id` FOREIGN KEY (`instance_id`) REFERENCES `sql_instance` (`id`) 34 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 35 | 36 | 37 | -- 增加实例数据库表 38 | CREATE TABLE `instance_database` ( 39 | `id` int(11) NOT NULL AUTO_INCREMENT, 40 | `db_name` varchar(128) NOT NULL COMMENT '账号', 41 | `owner` varchar(30) NOT NULL COMMENT '负责人', 42 | `owner_display` varchar(50) NOT NULL DEFAULT '负责人中文名', 43 | `remark` varchar(255) NOT NULL COMMENT '备注', 44 | `sys_time` datetime(6) NOT NULL COMMENT '系统时间', 45 | `instance_id` int(11) NOT NULL COMMENT '实例', 46 | PRIMARY KEY (`id`), 47 | CONSTRAINT `fk_database_sql_instance_id` FOREIGN KEY (`instance_id`) REFERENCES `sql_instance` (`id`) 48 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 49 | 50 | 51 | -------------------------------------------------------------------------------- /sql/completer/mysql.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @author: hhyo 4 | @license: Apache Licence 5 | @file: mysql.py 6 | @time: 2019/03/09 7 | """ 8 | import threading 9 | 10 | import mycli.sqlcompleter as completer 11 | from prompt_toolkit.document import Document 12 | from mycli.completion_refresher import CompletionRefresher 13 | from mycli.sqlexecute import SQLExecute 14 | from . import Completer 15 | 16 | __author__ = 'hhyo' 17 | 18 | 19 | class MysqlComEngine(Completer): 20 | def __init__(self, instance=None, db_name=None): 21 | self.instance = instance 22 | self.db_name = db_name 23 | self.completion_refresher = CompletionRefresher() 24 | self.completer = completer.SQLCompleter(smart_completion=True) 25 | self._completer_lock = threading.Lock() 26 | self.sql_execute = self._get_sql_execute() 27 | self.refresh_completions() 28 | 29 | def _get_sql_execute(self): 30 | """ 31 | 初始化数据库执行连接 32 | :return: 33 | """ 34 | return SQLExecute(self.db_name, self.instance.user, self.instance.password, self.instance.host, 35 | int(self.instance.port), 36 | socket='', charset=self.instance.charset or 'utf8mb4', 37 | local_infile='', ssl='', ssh_user='', ssh_host='', ssh_port='', 38 | ssh_password='', ssh_key_filename='' 39 | ) 40 | 41 | def refresh_completions(self, reset=False): 42 | """ 43 | 刷新completer对象元数据 44 | :param reset: 45 | :return: 46 | """ 47 | if reset: 48 | with self._completer_lock: 49 | self.completer.reset_completions() 50 | self.completion_refresher.refresh(self.sql_execute, self._on_completions_refreshed) 51 | return [(None, None, None, 'Auto-completion refresh started in the background.')] 52 | 53 | def _on_completions_refreshed(self, new_completer): 54 | """ 55 | 刷新completer对象回调函数,替换对象 56 | :param new_completer: 57 | :return: 58 | """ 59 | with self._completer_lock: 60 | self.completer = new_completer 61 | 62 | def get_completions(self, text, cursor_position): 63 | """ 64 | 获取补全提示 65 | :param text: 66 | :param cursor_position: 67 | :return: 68 | """ 69 | with self._completer_lock: 70 | return self.completer.get_completions( 71 | Document(text=text, cursor_position=cursor_position), None) 72 | -------------------------------------------------------------------------------- /sql/static/css/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- sidebar --------------------------------------------------------------- */ 13 | 14 | div.sphinxsidebarwrapper { 15 | padding: 10px 5px 0 10px; 16 | } 17 | 18 | div.sphinxsidebar { 19 | float: left; 20 | width: 230px; 21 | margin-left: -100%; 22 | font-size: 90%; 23 | word-wrap: break-word; 24 | overflow-wrap : break-word; 25 | } 26 | 27 | div.sphinxsidebar ul { 28 | list-style: none; 29 | } 30 | 31 | div.sphinxsidebar ul ul, 32 | div.sphinxsidebar ul.want-points { 33 | margin-left: 20px; 34 | list-style: square; 35 | } 36 | 37 | div.sphinxsidebar ul ul { 38 | margin-top: 0; 39 | margin-bottom: 0; 40 | } 41 | 42 | div.sphinxsidebar form { 43 | margin-top: 10px; 44 | } 45 | 46 | div.sphinxsidebar input { 47 | border: 1px solid #98dbcc; 48 | font-family: sans-serif; 49 | font-size: 1em; 50 | } 51 | 52 | div.sphinxsidebar #searchbox input[type="text"] { 53 | width: 170px; 54 | } 55 | 56 | /* -- general index --------------------------------------------------------- */ 57 | 58 | table.indextable { 59 | width: 100%; 60 | } 61 | 62 | table.indextable td { 63 | text-align: left; 64 | vertical-align: top; 65 | } 66 | 67 | table.indextable ul { 68 | margin-top: 0; 69 | margin-bottom: 0; 70 | list-style-type: none; 71 | } 72 | 73 | table.indextable > tbody > tr > td > ul { 74 | padding-left: 0em; 75 | } 76 | 77 | table.indextable tr.pcap { 78 | height: 10px; 79 | } 80 | 81 | table.indextable tr.cap { 82 | margin-top: 10px; 83 | background-color: #f2f2f2; 84 | } 85 | 86 | img.toggler { 87 | margin-right: 3px; 88 | margin-top: 3px; 89 | cursor: pointer; 90 | } 91 | 92 | div.modindex-jumpbox { 93 | border-top: 1px solid #ddd; 94 | border-bottom: 1px solid #ddd; 95 | margin: 1em 0 1em 0; 96 | padding: 0.4em; 97 | } 98 | 99 | div.genindex-jumpbox { 100 | border-top: 1px solid #ddd; 101 | border-bottom: 1px solid #ddd; 102 | margin: 1em 0 1em 0; 103 | padding: 0.4em; 104 | } 105 | 106 | /* -- domain module index --------------------------------------------------- */ 107 | 108 | table.modindextable td { 109 | padding: 2px; 110 | border-collapse: collapse; 111 | } 112 | -------------------------------------------------------------------------------- /src/charts/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "archery.fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "archery.name" . }} 7 | helm.sh/chart: {{ include "archery.chart" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: {{ .Release.Service }} 10 | spec: 11 | replicas: {{ .Values.replicaCount }} 12 | selector: 13 | matchLabels: 14 | app.kubernetes.io/name: {{ include "archery.name" . }} 15 | app.kubernetes.io/instance: {{ .Release.Name }} 16 | template: 17 | metadata: 18 | labels: 19 | app.kubernetes.io/name: {{ include "archery.name" . }} 20 | app.kubernetes.io/instance: {{ .Release.Name }} 21 | spec: 22 | initContainers: 23 | - image: busybox 24 | name: init-container 25 | imagePullPolicy: IfNotPresent 26 | args: 27 | - "mkdir" 28 | - "-p" 29 | - "/opt/archery/downloads/{binlog2sql,log,schemasync}" 30 | volumeMounts: 31 | - mountPath: /opt/archery/downloads 32 | name: archery-download 33 | - mountPath: /opt/archery/src/script 34 | name: archery-script 35 | - image: busybox 36 | name: init-mysql 37 | imagePullPolicy: IfNotPresent 38 | command: ['sh', '-c', 'until nslookup archery-mysql; do echo waiting for mysql; sleep 2; done;'] 39 | containers: 40 | - name: {{ .Chart.Name }} 41 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 42 | imagePullPolicy: {{ .Values.image.pullPolicy }} 43 | env: 44 | - name: NGINX_PORT 45 | value: "9123" 46 | ports: 47 | - name: archery 48 | containerPort: 9123 49 | protocol: TCP 50 | livenessProbe: 51 | httpGet: 52 | path: / 53 | port: archery 54 | lifecycle: 55 | postStart: 56 | exec: 57 | command: ['/bin/bash','/opt/archery/src/docker/init-archery.sh'] 58 | resources: 59 | {{- toYaml .Values.resources | nindent 12 }} 60 | {{- with .Values.volumeMounts }} 61 | volumeMounts: 62 | {{- toYaml . | nindent 12 }} 63 | {{- end }} 64 | {{- with .Values.volumes }} 65 | volumes: 66 | {{- toYaml . | nindent 8 }} 67 | {{- end }} 68 | {{- with .Values.nodeSelector }} 69 | nodeSelector: 70 | {{- toYaml . | nindent 8 }} 71 | {{- end }} 72 | {{- with .Values.affinity }} 73 | affinity: 74 | {{- toYaml . | nindent 8 }} 75 | {{- end }} 76 | {{- with .Values.tolerations }} 77 | tolerations: 78 | {{- toYaml . | nindent 8 }} 79 | {{- end }} 80 | -------------------------------------------------------------------------------- /common/static/ace/theme-textmate.js: -------------------------------------------------------------------------------- 1 | define("ace/theme/textmate",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";t.isDark=!1,t.cssClass="ace-tm",t.cssText='.ace-tm .ace_gutter {background: #f0f0f0;color: #333;}.ace-tm .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-tm .ace_fold {background-color: #6B72E6;}.ace-tm {background-color: #FFFFFF;color: black;}.ace-tm .ace_cursor {color: black;}.ace-tm .ace_invisible {color: rgb(191, 191, 191);}.ace-tm .ace_storage,.ace-tm .ace_keyword {color: blue;}.ace-tm .ace_constant {color: rgb(197, 6, 11);}.ace-tm .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-tm .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-tm .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-tm .ace_invalid {background-color: rgba(255, 0, 0, 0.1);color: red;}.ace-tm .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-tm .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-tm .ace_support.ace_type,.ace-tm .ace_support.ace_class {color: rgb(109, 121, 222);}.ace-tm .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-tm .ace_string {color: rgb(3, 106, 7);}.ace-tm .ace_comment {color: rgb(76, 136, 107);}.ace-tm .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-tm .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-tm .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-tm .ace_variable {color: rgb(49, 132, 149);}.ace-tm .ace_xml-pe {color: rgb(104, 104, 91);}.ace-tm .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-tm .ace_heading {color: rgb(12, 7, 255);}.ace-tm .ace_list {color:rgb(185, 6, 144);}.ace-tm .ace_meta.ace_tag {color:rgb(0, 22, 142);}.ace-tm .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-tm .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-tm.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;}.ace-tm .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-tm .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-tm .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-tm .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-tm .ace_gutter-active-line {background-color : #dcdcdc;}.ace-tm .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-tm .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}',t.$id="ace/theme/textmate";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}); (function() { 2 | window.require(["ace/theme/textmate"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /src/init_sql/v1.5.3_v1.6.0.sql: -------------------------------------------------------------------------------- 1 | -- 增加oracle实例相关字段 2 | ALTER TABLE sql_instance 3 | ADD `sid` varchar(50) DEFAULT NULL COMMENT 'Oracle sid' AFTER password, 4 | ADD `service_name` varchar(50) DEFAULT NULL COMMENT 'Oracle Service name' AFTER password; 5 | 6 | -- 变更字段名 7 | ALTER TABLE param_history CHANGE update_time create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '参数修改时间'; 8 | 9 | -- 变更字符集为utf8mb4,本身是utf8mb4的无需执行该语句 10 | ALTER TABLE sql_workflow_content 11 | modify `sql_content` longtext CHARACTER SET utf8mb4 NOT NULL COMMENT '提交的SQL文本', 12 | modify `review_content` longtext CHARACTER SET utf8mb4 NOT NULL COMMENT '自动审核内容的JSON格式', 13 | modify `execute_result` longtext CHARACTER SET utf8mb4 NOT NULL COMMENT '执行结果的JSON格式'; 14 | 15 | ALTER TABLE query_log 16 | modify `sqllog` longtext CHARACTER SET utf8mb4 NOT NULL COMMENT '执行的sql查询'; 17 | 18 | -- 增加实例标签配置 19 | CREATE TABLE `sql_instance_tag` ( 20 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '标签id', 21 | `tag_code` varchar(20) NOT NULL COMMENT '标签代码', 22 | `tag_name` varchar(20) NOT NULL COMMENT '标签名称', 23 | `active` tinyint(1) NOT NULL COMMENT '激活状态', 24 | `create_time` datetime(6) NOT NULL COMMENT '创建时间', 25 | PRIMARY KEY (`id`), 26 | UNIQUE KEY `uniq_tag_code` (`tag_code`), 27 | UNIQUE KEY `uniq_tag_name` (`tag_name`) 28 | ) COMMENT '实例标签配置' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 29 | 30 | CREATE TABLE `sql_instance_tag_relations` ( 31 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '关联id', 32 | `instance_id` int(11) NOT NULL COMMENT '关联实例ID', 33 | `instance_tag_id` int(11) NOT NULL COMMENT '关联标签ID', 34 | `active` tinyint(1) NOT NULL COMMENT '激活状态', 35 | `create_time` datetime(6) NOT NULL COMMENT '创建时间', 36 | PRIMARY KEY (`id`), 37 | KEY `idx_instance_tag_id` (`instance_tag_id`), 38 | UNIQUE KEY `uniq_instance_id_instance_tag_id` (`instance_id`,`instance_tag_id`), 39 | CONSTRAINT `fk_itr_instance` FOREIGN KEY (`instance_id`) REFERENCES `sql_instance` (`id`), 40 | CONSTRAINT `fk_itr_instance_tag` FOREIGN KEY (`instance_tag_id`) REFERENCES `sql_instance_tag` (`id`) 41 | ) COMMENT '实例标签关系' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 42 | 43 | -- 初始化标签数据 44 | INSERT INTO sql_instance_tag (id, tag_code, tag_name, active, create_time) VALUES (1, 'can_write', '支持上线', 1, '2019-05-03 00:00:00.000000'); 45 | INSERT INTO sql_instance_tag (id, tag_code, tag_name, active, create_time) VALUES (2, 'can_read', '支持查询', 1, '2019-05-03 00:00:00.000000'); 46 | 47 | -- 给原有主从数据增加标签 48 | insert into sql_instance_tag_relations (instance_id, instance_tag_id, active, create_time) 49 | select id,1,1,now() from sql_instance where type='master'; 50 | insert into sql_instance_tag_relations (instance_id, instance_tag_id, active, create_time) 51 | select id,2,1,now() from sql_instance where type='slave'; 52 | 53 | set @content_type_id=(select id from django_content_type where app_label='sql' and model='permission'); 54 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('可查询所有实例', @content_type_id, 'query_all_instances'); 55 | -------------------------------------------------------------------------------- /sql/fixtures/auth_group.sql: -------------------------------------------------------------------------------- 1 | -- sql_instance_tag 2 | INSERT INTO sql_instance_tag (id, tag_code, tag_name, active, create_time) 3 | VALUES (1, 'can_write', '支持上线', 1, now()),(2, 'can_read', '支持查询', 1, now()); 4 | 5 | -- auth_group 6 | INSERT INTO auth_group (id, name) 7 | VALUES (1, 'Default'),(2, 'RD'),(3, 'DBA'),(4, 'PM'),(5, 'QA'); 8 | 9 | SET FOREIGN_KEY_CHECKS=0; 10 | -- Default 11 | insert into auth_group_permissions (group_id, permission_id) 12 | select 1,id 13 | from auth_permission 14 | where codename in ('menu_sqlworkflow', 'menu_query', 'menu_sqlquery', 'menu_queryapplylist', 'menu_document'); 15 | 16 | -- RD 17 | insert into auth_group_permissions (group_id, permission_id) 18 | select 2,id 19 | from auth_permission 20 | where codename in ('menu_dashboard','menu_sqlcheck','menu_sqlworkflow','menu_sqlanalyze','menu_query','menu_sqlquery','menu_queryapplylist','menu_sqloptimize','menu_sqladvisor','menu_slowquery','menu_data_dictionary','menu_tools','menu_archive','menu_document','sql_submit','sql_execute','sql_analyze','optimize_sqladvisor','optimize_soar','query_applypriv','query_submit','archive_apply'); 21 | 22 | -- DBA 23 | insert into auth_group_permissions (group_id, permission_id) 24 | select 3,id 25 | from auth_permission 26 | where codename in ('menu_dashboard','menu_sqlcheck','menu_sqlworkflow','menu_sqlanalyze','menu_query','menu_sqlquery','menu_queryapplylist','menu_sqloptimize','menu_sqladvisor','menu_slowquery','menu_instance','menu_instance_list','menu_dbdiagnostic','menu_database','menu_instance_account','menu_param','menu_data_dictionary','menu_tools','menu_archive','menu_binlog2sql','menu_schemasync','menu_system','menu_document','sql_submit','sql_review','sql_execute_for_resource_group','sql_execute','sql_analyze','optimize_sqladvisor','optimize_sqltuning','optimize_soar','query_applypriv','query_mgtpriv','query_review','query_submit','query_all_instances','query_resource_group_instance','process_view','process_kill','tablespace_view','trx_view','trxandlocks_view','instance_account_manage','param_view','param_edit','data_dictionary_export','archive_apply','archive_review','archive_mgt'); 27 | 28 | -- PM 29 | insert into auth_group_permissions (group_id, permission_id) 30 | select 4,id 31 | from auth_permission 32 | where codename in ('menu_dashboard','menu_sqlcheck','menu_sqlworkflow','menu_sqlanalyze','menu_query','menu_sqlquery','menu_queryapplylist','menu_sqloptimize','menu_sqladvisor','menu_slowquery','menu_data_dictionary','menu_tools','menu_archive','menu_document','sql_submit','sql_review','sql_execute_for_resource_group','sql_execute','sql_analyze','optimize_sqladvisor','optimize_soar','query_applypriv','query_review','query_submit','archive_apply','archive_review'); 33 | 34 | -- QA 35 | insert into auth_group_permissions (group_id, permission_id) 36 | select 5,id 37 | from auth_permission 38 | where codename in ('menu_dashboard','menu_sqlcheck','menu_sqlworkflow','menu_query','menu_sqlquery','menu_queryapplylist','menu_data_dictionary','menu_document','sql_submit','sql_execute','query_applypriv','query_submit'); 39 | 40 | SET FOREIGN_KEY_CHECKS=1; 41 | -------------------------------------------------------------------------------- /common/workflow.py: -------------------------------------------------------------------------------- 1 | import simplejson as json 2 | from django.contrib.auth.models import Group 3 | from django.http import HttpResponse 4 | 5 | from common.utils.const import WorkflowDict 6 | from common.utils.extend_json_encoder import ExtendJSONEncoder 7 | from sql.models import WorkflowAudit, WorkflowLog 8 | from sql.utils.resource_group import user_groups 9 | 10 | 11 | # 获取审核列表 12 | def lists(request): 13 | # 获取用户信息 14 | user = request.user 15 | 16 | limit = int(request.POST.get('limit')) 17 | offset = int(request.POST.get('offset')) 18 | workflow_type = int(request.POST.get('workflow_type')) 19 | limit = offset + limit 20 | search = request.POST.get('search', '') 21 | 22 | # 先获取用户所在资源组列表 23 | group_list = user_groups(user) 24 | group_ids = [group.group_id for group in group_list] 25 | # 再获取用户所在权限组列表 26 | if user.is_superuser: 27 | auth_group_ids = [group.id for group in Group.objects.all()] 28 | else: 29 | auth_group_ids = [group.id for group in Group.objects.filter(user=user)] 30 | 31 | # 只返回所在资源组当前待自己审核的数据 32 | workflow_audit = WorkflowAudit.objects.filter( 33 | workflow_title__icontains=search, 34 | current_status=WorkflowDict.workflow_status['audit_wait'], 35 | group_id__in=group_ids, 36 | current_audit__in=auth_group_ids 37 | ) 38 | # 过滤工单类型 39 | if workflow_type != 0: 40 | workflow_audit = workflow_audit.filter(workflow_type=workflow_type) 41 | 42 | audit_list_count = workflow_audit.count() 43 | audit_list = workflow_audit.order_by('-audit_id')[offset:limit].values( 44 | 'audit_id', 'workflow_type', 45 | 'workflow_title', 'create_user_display', 46 | 'create_time', 'current_status', 47 | 'audit_auth_groups', 48 | 'current_audit', 49 | 'group_name') 50 | 51 | # QuerySet 序列化 52 | rows = [row for row in audit_list] 53 | 54 | result = {"total": audit_list_count, "rows": rows} 55 | # 返回查询结果 56 | return HttpResponse(json.dumps(result, cls=ExtendJSONEncoder, bigint_as_string=True), 57 | content_type='application/json') 58 | 59 | 60 | # 获取工单日志 61 | def log(request): 62 | workflow_id = request.POST.get('workflow_id') 63 | workflow_type = request.POST.get('workflow_type') 64 | try: 65 | audit_id = WorkflowAudit.objects.get(workflow_id=workflow_id, workflow_type=workflow_type).audit_id 66 | workflow_logs = WorkflowLog.objects.filter(audit_id=audit_id).order_by('-id').values( 67 | 'operation_type_desc', 68 | 'operation_info', 69 | 'operator_display', 70 | 'operation_time') 71 | count = WorkflowLog.objects.filter(audit_id=audit_id).count() 72 | except Exception: 73 | workflow_logs = [] 74 | count = 0 75 | 76 | # QuerySet 序列化 77 | rows = [row for row in workflow_logs] 78 | result = {"total": count, "rows": rows} 79 | # 返回查询结果 80 | return HttpResponse(json.dumps(result, cls=ExtendJSONEncoder, bigint_as_string=True), 81 | content_type='application/json') 82 | -------------------------------------------------------------------------------- /admin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ######################################################################### 4 | # Update Time : 2020-02-25 5 | # Author: alenx 6 | ######################################################################### 7 | 8 | function init() { 9 | echo "Initing archery" 10 | echo "----------------" 11 | echo "安装/更新可能缺少的依赖: mysql-community-devel gcc gcc-c++ python-devel" 12 | sudo yum install -y epel-release 13 | sudo yum install -y mysql-devel gcc gcc-c++ python-devel MySQL-python 14 | sudo yum install -y python36 python3-devel python36-pip openldap-devel unixODBC-devel gettext 15 | 16 | python3 -m pip install virtualenv -i https://mirrors.aliyun.com/pypi/simple/ 17 | if [ ! -d "venv" ]; then 18 | virtualenv --system-site-packages -p python3 venv 19 | fi 20 | source ./venv/bin/activate 21 | ./venv/bin/python3 -m pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ 22 | echo "************************************************" 23 | echo -e "\033[32m init archery success \033[0m" 24 | echo -e "\033[32m welcome to archery 2.0 \033[0m" 25 | } 26 | 27 | function start() { 28 | echo "Starting archery" 29 | echo "----------------" 30 | source ./venv/bin/activate 31 | python3 manage.py collectstatic -v0 --noinput 32 | supervisord -c supervisord.conf 33 | echo -e "Start archery: [\033[32m ok \033[0m]" 34 | } 35 | 36 | function stop() { 37 | echo "Stoping archery" 38 | echo "----------------" 39 | source ./venv/bin/activate 40 | supervisorctl -c supervisord.conf stop all 41 | kill -9 $(ps -ef | grep "Archery" | grep -v grep | awk '{print $2}') 42 | echo -e "Stop archery: [\033[32m ok \033[0m]" 43 | } 44 | 45 | function restart() { 46 | stop 47 | echo "" 48 | start 49 | } 50 | 51 | function adduser() { 52 | echo "Add Admin Users " 53 | source ./venv/bin/activate 54 | python3 manage.py createsuperuser 55 | echo -e "Add Users: [\033[32m ok \033[0m]" 56 | } 57 | 58 | function migration() { 59 | echo "Migration archery" 60 | echo "----------------" 61 | source ./venv/bin/activate 62 | python3 manage.py makemigrations sql 63 | python3 manage.py migrate 64 | python3 manage.py dbshell" + gettext(value) + "" 25 | } 26 | else if (value === "workflow_abort") { 27 | return "" + gettext(value) + "" 28 | } 29 | else if (value === "workflow_manreviewing") { 30 | return "" + gettext(value) + "" 31 | } 32 | else if (value === "workflow_review_pass") { 33 | return "" + gettext(value) + "" 34 | } 35 | else if (value === "workflow_timingtask") { 36 | return "" + gettext(value) + "" 37 | } 38 | else if (value === "workflow_executing") { 39 | return "" + gettext(value) + "" 40 | } 41 | else if (value === "workflow_autoreviewwrong") { 42 | return "" + gettext(value) + "" 43 | } 44 | else if (value === "workflow_exception") { 45 | return "" + gettext(value) + "" 46 | } 47 | else if (value === "workflow_finish_manual") { 48 | return "" + gettext(value) + "" 49 | } 50 | else { 51 | return "" + "未知状态" + "" 52 | } 53 | } 54 | 55 | function workflow_type_formatter(value) { 56 | if (value === workflow_type.query) { 57 | return workflow_type.query_display 58 | } 59 | else if (value === workflow_type.sqlreview) { 60 | return workflow_type.sqlreview_display 61 | } 62 | else { 63 | return '未知状态' 64 | } 65 | } 66 | 67 | function workflow_status_formatter(value) { 68 | if (value === workflow_status.audit_wait) { 69 | return "" + workflow_status.audit_wait_display + " " 70 | } 71 | else if (value === workflow_status.audit_success) { 72 | return " " + workflow_status.audit_success_display + " " 73 | } 74 | else if (value === workflow_status.audit_reject) { 75 | return "" + workflow_status.audit_reject_display + " " 76 | } 77 | else if (value === workflow_status.audit_abort) { 78 | return "" + workflow_status.audit_abort_display + " " 79 | } 80 | else { 81 | return "" + '未知状态' + "" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /common/static/ace/theme-mongodb.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | ace.define("ace/theme/mongodb",["require","exports","module","ace/lib/dom"], function(acequire, exports, module) { 3 | 4 | exports.isDark = false; 5 | exports.cssClass = "ace-mongodb"; 6 | exports.cssText = "\ 7 | .ace-mongodb .ace_gutter {\ 8 | background: #f5f6f7;\ 9 | color: #999999;\ 10 | }\ 11 | .ace-mongodb {\ 12 | background: #f5f6f7;\ 13 | color: #000;\ 14 | }\ 15 | .ace-mongodb .ace_keyword {\ 16 | color: #999999;\ 17 | font-weight: normal;\ 18 | }\ 19 | .ace-mongodb .ace_gutter-cell {\ 20 | padding-left: 5px;\ 21 | padding-right: 10px;\ 22 | }\ 23 | .ace-mongodb .ace_string {\ 24 | color: #5b81a9;\ 25 | }\ 26 | .ace-mongodb .ace_boolean {\ 27 | color: #5b81a9;\ 28 | font-weight: normal;\ 29 | }\ 30 | .ace-mongodb .ace_constant.ace_numeric {\ 31 | color: #5b81a9;\ 32 | }\ 33 | .ace-mongodb .ace_string.ace_regexp {\ 34 | color: #5b81a9;\ 35 | }\ 36 | .ace-mongodb .ace_variable.ace_class {\ 37 | color: teal;\ 38 | }\ 39 | .ace-mongodb .ace_constant.ace_buildin {\ 40 | color: #0086B3;\ 41 | }\ 42 | .ace-mongodb .ace_support.ace_function {\ 43 | color: #0086B3;\ 44 | }\ 45 | .ace-mongodb .ace_comment {\ 46 | color: #998;\ 47 | font-style: italic;\ 48 | }\ 49 | .ace-mongodb .ace_variable.ace_language {\ 50 | color: #0086B3;\ 51 | }\ 52 | .ace-mongodb .ace_paren {\ 53 | font-weight: normal;\ 54 | }\ 55 | .ace-mongodb .ace_variable.ace_instance {\ 56 | color: teal;\ 57 | }\ 58 | .ace-mongodb .ace_constant.ace_language {\ 59 | font-weight: bold;\ 60 | }\ 61 | .ace-mongodb .ace_cursor {\ 62 | color: #999999;\ 63 | }\ 64 | .ace-mongodb.ace_focus .ace_marker-layer .ace_active-line {\ 65 | background: #f5f6f7;\ 66 | }\ 67 | .ace-mongodb .ace_marker-layer .ace_active-line {\ 68 | background: rgb(245, 245, 245);\ 69 | }\ 70 | .ace-mongodb .ace_marker-layer .ace_selection {\ 71 | background: rgb(181, 213, 255);\ 72 | }\ 73 | .ace-mongodb.ace_multiselect .ace_selection.ace_start {\ 74 | box-shadow: 0 0 3px 0px white;\ 75 | }\ 76 | .ace-mongodb.ace_nobold .ace_line > span {\ 77 | font-weight: normal !important;\ 78 | }\ 79 | .ace-mongodb .ace_marker-layer .ace_step {\ 80 | background: rgb(252, 255, 0);\ 81 | }\ 82 | .ace-mongodb .ace_marker-layer .ace_stack {\ 83 | background: rgb(164, 229, 101);\ 84 | }\ 85 | .ace-mongodb .ace_marker-layer .ace_bracket {\ 86 | margin: -1px 0 0 -1px;\ 87 | border: 1px solid rgb(192, 192, 192);\ 88 | }\ 89 | .ace-mongodb .ace_gutter-active-line {\ 90 | background: #f5f6f7;\ 91 | }\ 92 | .ace-mongodb .ace_marker-layer .ace_selected-word {\ 93 | background: rgb(250, 250, 255);\ 94 | border: 1px solid rgb(200, 200, 250);\ 95 | }\ 96 | .ace-mongodb .ace_invisible {\ 97 | color: #BFBFBF\ 98 | }\ 99 | .ace-mongodb .ace_print-margin {\ 100 | width: 1px;\ 101 | background: #e8e8e8;\ 102 | }\ 103 | .ace-mongodb .ace_hidden-cursors {\ 104 | opacity: 0;\ 105 | }\ 106 | .ace-mongodb .ace_indent-guide {\ 107 | background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==\") right repeat-y;\ 108 | }"; 109 | var dom = acequire("../lib/dom"); 110 | dom.importCssString(exports.cssText, exports.cssClass); 111 | }); 112 | -------------------------------------------------------------------------------- /src/docker/Dockerfile-base: -------------------------------------------------------------------------------- 1 | FROM docker.io/centos:7 2 | 3 | ENV PYTHON_VERSION 3.6.5 4 | ENV DOCKERIZE_VERSION v0.6.1 5 | ENV SOAR_VERSION 0.11.0 6 | 7 | ENV TZ=Asia/Shanghai 8 | ENV LANG en_US.UTF-8 9 | 10 | WORKDIR /opt 11 | 12 | #locale 13 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \ 14 | && yum -y install kde-l10n-Chinese \ 15 | && localedef -c -f UTF-8 -i zh_CN zh_CN.utf8 16 | 17 | ENV LC_ALL zh_CN.utf8 18 | 19 | #python 20 | RUN yum -y install libffi-devel wget gcc make zlib-devel openssl openssl-devel ncurses-devel openldap-devel gettext \ 21 | && cd /opt \ 22 | && wget "https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tar.xz" \ 23 | && tar -xvJf Python-$PYTHON_VERSION.tar.xz \ 24 | && cd /opt/Python-$PYTHON_VERSION \ 25 | && ./configure prefix=/usr/local/python3 \ 26 | && make && make install \ 27 | && ln -fs /usr/local/python3/bin/python3 /usr/bin/python3 \ 28 | && ln -fs /usr/local/python3/bin/pip3 /usr/bin/pip3 \ 29 | && pip3 install virtualenv \ 30 | && cd /opt \ 31 | && ln -fs /usr/local/python3/bin/virtualenv /usr/bin/virtualenv \ 32 | && virtualenv venv4archery --python=python3 \ 33 | && rm -rf Python-$PYTHON_VERSION \ 34 | && rm -rf Python-$PYTHON_VERSION.tar.xz \ 35 | #dockerize 36 | && wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ 37 | && tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ 38 | && rm dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ 39 | #sqladvisor 40 | && yum -y install epel-release \ 41 | && yum -y install cmake bison gcc-c++ git mysql-devel libaio-devel glib2 glib2-devel \ 42 | && yum -y install https://repo.percona.com/yum/percona-release-latest.noarch.rpm \ 43 | && yum -y install Percona-Server-devel-56 Percona-Server-shared-56 Percona-Server-client-56 \ 44 | && yum -y install percona-toolkit \ 45 | && cd /opt \ 46 | && git clone https://github.com/hhyo/SQLAdvisor.git --depth 3 \ 47 | && cd /opt/SQLAdvisor/ \ 48 | && cmake -DBUILD_CONFIG=mysql_release -DCMAKE_BUILD_TYPE=debug -DCMAKE_INSTALL_PREFIX=/usr/local/sqlparser ./ \ 49 | && make && make install \ 50 | && cd sqladvisor/ \ 51 | && cmake -DCMAKE_BUILD_TYPE=debug ./ \ 52 | && make \ 53 | && mv /opt/SQLAdvisor/sqladvisor/sqladvisor /opt \ 54 | && rm -rf /opt/SQLAdvisor/ 55 | #soar 56 | RUN cd /opt \ 57 | && wget https://github.com/XiaoMi/soar/releases/download/$SOAR_VERSION/soar.linux-amd64 -O soar \ 58 | && chmod a+x soar \ 59 | #binlog2sql 60 | && cd /opt \ 61 | && git clone https://github.com/danfengcao/binlog2sql.git \ 62 | && mv binlog2sql/binlog2sql/ tmp_binlog2sql \ 63 | && rm -rf binlog2sql \ 64 | #msodbc 65 | && cd /opt \ 66 | && curl https://packages.microsoft.com/config/rhel/7/prod.repo > /etc/yum.repos.d/mssql-release.repo \ 67 | && ACCEPT_EULA=Y yum -y install msodbcsql17 \ 68 | && yum -y install unixODBC-devel \ 69 | #oracle instantclient 70 | && yum -y install http://yum.oracle.com/repo/OracleLinux/OL7/oracle/instantclient/x86_64/getPackage/oracle-instantclient19.3-basiclite-19.3.0.0.0-1.x86_64.rpm 71 | 72 | -------------------------------------------------------------------------------- /sql/engines/mongo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | import re 3 | import pymongo 4 | import logging 5 | import traceback 6 | import json 7 | 8 | from . import EngineBase 9 | from .models import ResultSet 10 | from bson import json_util 11 | from pymongo.errors import OperationFailure 12 | 13 | __author__ = 'jackie' 14 | 15 | logger = logging.getLogger('default') 16 | 17 | 18 | class MongoEngine(EngineBase): 19 | def get_connection(self, db_name=None): 20 | self.db_name = self.db_name or 'admin' 21 | conn = pymongo.MongoClient(self.host, self.port, authSource=self.db_name, connect=True, connectTimeoutMS=10000) 22 | if self.user and self.password: 23 | conn[self.db_name].authenticate(self.user, self.password, self.db_name) 24 | return conn 25 | 26 | @property 27 | def name(self): # pragma: no cover 28 | return 'Mongo' 29 | 30 | @property 31 | def info(self): # pragma: no cover 32 | return 'Mongo engine' 33 | 34 | def get_all_databases(self): 35 | result = ResultSet() 36 | conn = self.get_connection() 37 | try: 38 | result.rows = conn.list_database_names() 39 | except OperationFailure: 40 | result.rows = [self.db_name] 41 | return result 42 | 43 | def get_all_tables(self, db_name, **kwargs): 44 | result = ResultSet() 45 | conn = self.get_connection() 46 | db = conn[db_name] 47 | result.rows = db.list_collection_names() 48 | return result 49 | 50 | def query_check(self, db_name=None, sql=''): 51 | """提交查询前的检查""" 52 | result = {'msg': '', 'bad_query': True, 'filtered_sql': sql, 'has_star': False} 53 | safe_cmd = ['find'] 54 | sql = sql.split('.')[1] 55 | for cmd in safe_cmd: 56 | if re.match(fr'^{cmd}\(.*', sql.strip(), re.I): 57 | result['bad_query'] = False 58 | break 59 | if result['bad_query']: 60 | result['msg'] = """禁止执行该命令!正确格式为:{collection_name}.find() or {collection_name}.find(expression)""", \ 61 | """如 : 'test.find({"id":{"$gt":1.0}})'""" 62 | return result 63 | 64 | def query(self, db_name=None, sql='', limit_num=0, close_conn=True, **kwargs): 65 | result_set = ResultSet(full_sql=sql) 66 | try: 67 | conn = self.get_connection() 68 | db = conn[db_name] 69 | collect = db[sql.split('.')[0]] 70 | match = re.compile(r'[(](.*)[)]', re.S) 71 | sql = re.findall(match, sql)[0] 72 | if sql != '': 73 | sql = json.loads(sql) 74 | result = collect.find(sql).limit(limit_num) 75 | else: 76 | result = collect.find(sql).limit(limit_num) 77 | rows = json.loads(json_util.dumps(result)) 78 | result_set.column_list = ['Result'] 79 | if isinstance(rows, list): 80 | result_set.rows = tuple([json.dumps(x, ensure_ascii=False)] for x in rows) 81 | result_set.affected_rows = len(rows) 82 | except Exception as e: 83 | logger.warning(f"Mongo命令执行报错,语句:{sql}, 错误信息:{traceback.format_exc()}") 84 | result_set.error = str(e) 85 | return result_set 86 | -------------------------------------------------------------------------------- /sql/sql_analyze.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @author: hhyo 4 | @license: Apache Licence 5 | @file: sql_analyze.py 6 | @time: 2019/03/14 7 | """ 8 | import simplejson as json 9 | from django.contrib.auth.decorators import permission_required 10 | 11 | from common.config import SysConfig 12 | from sql.plugins.soar import Soar 13 | from sql.utils.resource_group import user_instances 14 | from sql.utils.sql_utils import generate_sql 15 | from django.http import HttpResponse, JsonResponse 16 | from common.utils.extend_json_encoder import ExtendJSONEncoder 17 | from .models import Instance 18 | 19 | __author__ = 'hhyo' 20 | 21 | 22 | @permission_required('sql.sql_analyze', raise_exception=True) 23 | def generate(request): 24 | """ 25 | 解析上传文件为SQL列表 26 | :param request: 27 | :return: 28 | """ 29 | text = request.POST.get('text') 30 | if text is None: 31 | result = {"total": 0, "rows": []} 32 | else: 33 | rows = generate_sql(text) 34 | result = {"total": len(rows), "rows": rows} 35 | return HttpResponse(json.dumps(result, cls=ExtendJSONEncoder, bigint_as_string=True), 36 | content_type='application/json') 37 | 38 | 39 | @permission_required('sql.sql_analyze', raise_exception=True) 40 | def analyze(request): 41 | """ 42 | 利用soar分析SQL 43 | :param request: 44 | :return: 45 | """ 46 | text = request.POST.get('text') 47 | instance_name = request.POST.get('instance_name') 48 | db_name = request.POST.get('db_name') 49 | if not text: 50 | result = {"total": 0, "rows": []} 51 | else: 52 | soar = Soar() 53 | if instance_name != '' and db_name != '': 54 | try: 55 | instance_info = user_instances(request.user, db_type=['mysql']).get(instance_name=instance_name) 56 | except Instance.DoesNotExist: 57 | return JsonResponse({'status': 1, 'msg': '你所在组未关联该实例!', 'data': []}) 58 | soar_test_dsn = SysConfig().get('soar_test_dsn') 59 | # 获取实例连接信息 60 | online_dsn = "{user}:{pwd}@{host}:{port}/{db}".format(user=instance_info.user, 61 | pwd=instance_info.password, 62 | host=instance_info.host, 63 | port=instance_info.port, 64 | db=db_name) 65 | else: 66 | online_dsn = '' 67 | soar_test_dsn = '' 68 | args = {"report-type": "markdown", 69 | "query": '', 70 | "online-dsn": online_dsn, 71 | "test-dsn": soar_test_dsn, 72 | "allow-online-as-test": "false"} 73 | rows = generate_sql(text) 74 | for row in rows: 75 | args['query'] = row['sql'].replace('"', '\\"').replace('`', '').replace('\n', ' ') 76 | cmd_args = soar.generate_args2cmd(args=args, shell=True) 77 | stdout, stderr = soar.execute_cmd(cmd_args, shell=True).communicate() 78 | row['report'] = stdout if stdout else stderr 79 | result = {"total": len(rows), "rows": rows} 80 | return HttpResponse(json.dumps(result, cls=ExtendJSONEncoder, bigint_as_string=True), 81 | content_type='application/json') 82 | -------------------------------------------------------------------------------- /src/init_sql/v1.7.4_v1.7.5.sql: -------------------------------------------------------------------------------- 1 | -- 增加归档相关权限 2 | set @content_type_id=(select id from django_content_type where app_label='sql' and model='permission'); 3 | insert IGNORE INTO auth_permission (name, content_type_id, codename) VALUES 4 | ('菜单 数据归档', @content_type_id, 'menu_archive'), 5 | ('提交归档申请', @content_type_id, 'archive_apply'), 6 | ('审核归档申请', @content_type_id, 'archive_audit'), 7 | ('管理归档申请', @content_type_id, 'archive_mgt'); 8 | 9 | -- 归档配置表 10 | CREATE TABLE `archive_config` ( 11 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 12 | `title` varchar(50) NOT NULL COMMENT '归档配置说明', 13 | `resource_group_id` int(11) NOT NULL COMMENT '资源组', 14 | `audit_auth_groups` varchar(255) NOT NULL COMMENT '审批权限组列表', 15 | `src_instance_id` int(11) NOT NULL COMMENT '源实例', 16 | `src_db_name` varchar(64) NOT NULL COMMENT '源数据库', 17 | `src_table_name` varchar(64) NOT NULL COMMENT '源表', 18 | `dest_instance_id` int(11) DEFAULT NULL COMMENT '目标实例', 19 | `dest_db_name` varchar(64) DEFAULT NULL COMMENT '目标数据库', 20 | `dest_table_name` varchar(64) DEFAULT NULL COMMENT '目标表', 21 | `condition` varchar(1000) NOT NULL COMMENT '归档条件,where条件', 22 | `mode` varchar(10) NOT NULL COMMENT '归档模式', 23 | `no_delete` tinyint(1) NOT NULL COMMENT '是否保留源数据', 24 | `sleep` int(11) NOT NULL COMMENT '归档limit行记录后的休眠秒数', 25 | `status` int(11) NOT NULL COMMENT '审核状态', 26 | `state` tinyint(1) NOT NULL COMMENT '是否启用归档', 27 | `user_name` varchar(30) NOT NULL COMMENT '申请人', 28 | `user_display` varchar(50) NOT NULL COMMENT '申请人中文名', 29 | `create_time` datetime(6) NOT NULL COMMENT '创建时间', 30 | `last_archive_time` datetime(6) DEFAULT NULL COMMENT '最近归档时间', 31 | `sys_time` datetime(6) NOT NULL COMMENT '系统时间修改', 32 | PRIMARY KEY (`id`), 33 | KEY `idx_dest_instance_id` (`dest_instance_id`), 34 | KEY `idx_resource_group_id` (`resource_group_id`), 35 | KEY `idx_src_instance_id` (`src_instance_id`), 36 | CONSTRAINT `fk_archive_dest_instance_id` FOREIGN KEY (`dest_instance_id`) REFERENCES `sql_instance` (`id`), 37 | CONSTRAINT `fk_archive_resource_id` FOREIGN KEY (`resource_group_id`) REFERENCES `resource_group` (`group_id`), 38 | CONSTRAINT `fk_archive_src_instance_id` FOREIGN KEY (`src_instance_id`) REFERENCES `sql_instance` (`id`) 39 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '归档配置表'; 40 | 41 | -- 归档日志表 42 | CREATE TABLE `archive_log` ( 43 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 44 | `archive_id` int(11) NOT NULL COMMENT '归档配置ID', 45 | `cmd` varchar(2000) NOT NULL COMMENT '归档命令', 46 | `condition` varchar(1000) NOT NULL COMMENT '归档条件', 47 | `mode` varchar(10) NOT NULL COMMENT '归档模式', 48 | `no_delete` tinyint(1) NOT NULL COMMENT '是否保留源数据', 49 | `sleep` int(11) NOT NULL COMMENT '归档limit行记录后的休眠秒数', 50 | `select_cnt` int(11) NOT NULL COMMENT '查询数量', 51 | `insert_cnt` int(11) NOT NULL COMMENT '插入数量', 52 | `delete_cnt` int(11) NOT NULL COMMENT '删除数量', 53 | `statistics` longtext NOT NULL COMMENT '归档统计日志', 54 | `success` tinyint(1) NOT NULL COMMENT '是否归档成功', 55 | `error_info` longtext NOT NULL COMMENT '错误信息', 56 | `start_time` datetime(6) NOT NULL COMMENT '开始时间', 57 | `end_time` datetime(6) NOT NULL COMMENT '结束时间', 58 | `sys_time` datetime(6) NOT NULL COMMENT '系统时间修改', 59 | PRIMARY KEY (`id`), 60 | KEY `idx_archive_id` (`archive_id`), 61 | CONSTRAINT `fk_archive_config_id` FOREIGN KEY (`archive_id`) REFERENCES `archive_config` (`id`) 62 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '归档日志表'; 63 | -------------------------------------------------------------------------------- /src/init_sql/v1.4.5_v1.5.0.sql: -------------------------------------------------------------------------------- 1 | -- 增加权限 2 | set @content_type_id=(select id from django_content_type where app_label='sql' and model='permission'); 3 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('菜单 参数配置', @content_type_id, 'menu_param'); 4 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('查看实例参数列表', @content_type_id, 'param_view'); 5 | INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('修改实例参数', @content_type_id, 'param_edit'); 6 | 7 | -- 修改布尔值 8 | -- sql_workflow.is_backup 9 | UPDATE sql_workflow SET is_backup=1 WHERE is_backup='是'; 10 | UPDATE sql_workflow SET is_backup=0 WHERE is_backup='否'; 11 | ALTER TABLE sql_workflow 12 | MODIFY is_backup TINYINT NOT NULL DEFAULT 1 COMMENT '是否备份'; 13 | 14 | -- data_masking_columns.active 15 | ALTER TABLE data_masking_columns 16 | MODIFY active TINYINT NOT NULL DEFAULT 0 COMMENT '激活状态'; 17 | 18 | -- query_log.masking 19 | UPDATE query_log SET priv_check=0 WHERE priv_check=2; 20 | UPDATE query_log SET hit_rule=0 WHERE hit_rule=2; 21 | UPDATE query_log SET masking=0 WHERE masking=2; 22 | ALTER TABLE query_log 23 | MODIFY priv_check TINYINT NOT NULL DEFAULT 0 COMMENT '查询权限是否正常校验', 24 | MODIFY hit_rule TINYINT NOT NULL DEFAULT 0 COMMENT '查询是否命中脱敏规则', 25 | MODIFY masking TINYINT NOT NULL DEFAULT 0 COMMENT '查询结果是否正常脱敏'; 26 | 27 | -- aliyun_access_key.is_enable 28 | UPDATE aliyun_access_key SET is_enable=0 WHERE is_enable=2; 29 | ALTER TABLE aliyun_access_key 30 | MODIFY is_enable TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用'; 31 | 32 | -- aliyun_rds_config.is_enable 33 | UPDATE aliyun_rds_config SET is_enable=0 WHERE is_enable=2; 34 | ALTER TABLE aliyun_rds_config 35 | MODIFY is_enable TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用'; 36 | 37 | -- 用户名和密码增加默认值 38 | ALTER TABLE sql_instance 39 | MODIFY `user` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '用户名', 40 | MODIFY `password` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '密码'; 41 | 42 | -- 用户权限表增加索引 43 | ALTER TABLE query_privileges 44 | ADD INDEX idx_user_name_instance_id_db_name_valid_date(user_name,instance_id,db_name,valid_date); 45 | 46 | -- 实例参数配置功能 47 | CREATE TABLE param_template( 48 | id INT NOT NULL AUTO_INCREMENT PRIMARY KEY , 49 | db_type VARCHAR(10) NOT NULL COMMENT '数据库类型,mysql、mssql、redis、pgsql', 50 | variable_name VARCHAR(64) NOT NULL COMMENT '参数名', 51 | default_value VARCHAR(1024) NOT NULL COMMENT '默认参数值', 52 | editable TINYINT NOT NULL COMMENT '是否支持修改', 53 | valid_values VARCHAR(1024) NOT NULL COMMENT '有效参数值,', 54 | description VARCHAR(1024) NOT NULL COMMENT '参数描述', 55 | create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 56 | sys_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', 57 | UNIQUE uniq_db_type_variable_name(db_type, variable_name) 58 | ) COMMENT '实例参数配置表'; 59 | 60 | 61 | 62 | CREATE TABLE param_history( 63 | id INT NOT NULL AUTO_INCREMENT PRIMARY KEY , 64 | instance_id INT NOT NULL COMMENT '实例ID', 65 | variable_name VARCHAR(64) NOT NULL COMMENT '参数名', 66 | old_var VARCHAR(1024) NOT NULL COMMENT '修改前参数值', 67 | new_var VARCHAR(1024) NOT NULL COMMENT '修改后参数值', 68 | set_sql VARCHAR(1024) NOT NULL COMMENT '在线变更配置执行的SQL语句', 69 | user_name VARCHAR(30) NOT NULL COMMENT '修改人', 70 | user_display VARCHAR(50) NOT NULL COMMENT '修改人中文名', 71 | update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', 72 | INDEX idx_variable_name(variable_name), 73 | CONSTRAINT fk_param_instance FOREIGN KEY fk_param_instance (instance_id) REFERENCES sql_instance (id) ON DELETE RESTRICT ON UPDATE RESTRICT 74 | ) COMMENT '实例参数修改历史'; 75 | -------------------------------------------------------------------------------- /sql/utils/execute_sql.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | 3 | from django.db import close_old_connections, connection 4 | from django_redis import get_redis_connection 5 | from common.utils.const import WorkflowDict 6 | from sql.engines.models import ReviewResult, ReviewSet 7 | from sql.models import SqlWorkflow 8 | from sql.notify import notify_for_execute 9 | from sql.utils.workflow_audit import Audit 10 | from sql.engines import get_engine 11 | 12 | 13 | def execute(workflow_id): 14 | """为延时或异步任务准备的execute, 传入工单ID即可""" 15 | workflow_detail = SqlWorkflow.objects.get(id=workflow_id) 16 | # 给定时执行的工单增加执行日志 17 | if workflow_detail.status == 'workflow_timingtask': 18 | # 将工单状态修改为执行中 19 | SqlWorkflow(id=workflow_id, status='workflow_executing').save(update_fields=['status']) 20 | audit_id = Audit.detail_by_workflow_id(workflow_id=workflow_id, 21 | workflow_type=WorkflowDict.workflow_type['sqlreview']).audit_id 22 | Audit.add_log(audit_id=audit_id, 23 | operation_type=5, 24 | operation_type_desc='执行工单', 25 | operation_info='系统定时执行', 26 | operator='', 27 | operator_display='系统' 28 | ) 29 | execute_engine = get_engine(instance=workflow_detail.instance) 30 | return execute_engine.execute_workflow(workflow=workflow_detail) 31 | 32 | 33 | def execute_callback(task): 34 | """异步任务的回调, 将结果填入数据库等等 35 | 使用django-q的hook, 传入参数为整个task 36 | task.result 是真正的结果 37 | """ 38 | # https://stackoverflow.com/questions/7835272/django-operationalerror-2006-mysql-server-has-gone-away 39 | if connection.connection and not connection.is_usable(): 40 | close_old_connections() 41 | workflow_id = task.args[0] 42 | workflow = SqlWorkflow.objects.get(id=workflow_id) 43 | workflow.finish_time = task.stopped 44 | 45 | if not task.success: 46 | # 不成功会返回错误堆栈信息,构造一个错误信息 47 | workflow.status = 'workflow_exception' 48 | execute_result = ReviewSet(full_sql=workflow.sqlworkflowcontent.sql_content) 49 | execute_result.rows = [ReviewResult( 50 | stage='Execute failed', 51 | errlevel=2, 52 | stagestatus='异常终止', 53 | errormessage=task.result, 54 | sql=workflow.sqlworkflowcontent.sql_content)] 55 | elif task.result.warning or task.result.error: 56 | execute_result = task.result 57 | workflow.status = 'workflow_exception' 58 | else: 59 | execute_result = task.result 60 | workflow.status = 'workflow_finish' 61 | # 保存执行结果 62 | workflow.sqlworkflowcontent.execute_result = execute_result.json() 63 | workflow.sqlworkflowcontent.save() 64 | workflow.save() 65 | 66 | # 增加工单日志 67 | audit_id = Audit.detail_by_workflow_id(workflow_id=workflow_id, 68 | workflow_type=WorkflowDict.workflow_type['sqlreview']).audit_id 69 | Audit.add_log(audit_id=audit_id, 70 | operation_type=6, 71 | operation_type_desc='执行结束', 72 | operation_info='执行结果:{}'.format(workflow.get_status_display()), 73 | operator='', 74 | operator_display='系统' 75 | ) 76 | 77 | # DDL工单结束后清空实例资源缓存 78 | if workflow.syntax_type == 1: 79 | r = get_redis_connection("default") 80 | for key in r.scan_iter(match='*insRes*', count=2000): 81 | r.delete(key) 82 | 83 | # 发送消息 84 | notify_for_execute(workflow) 85 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at rtttte@qq.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /common/utils/aliyun_sdk.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | import datetime 3 | import traceback 4 | 5 | from aliyunsdkcore.client import AcsClient 6 | from aliyunsdkrds.request.v20140815 import DescribeSlowLogsRequest, DescribeSlowLogRecordsRequest, \ 7 | RequestServiceOfCloudDBARequest 8 | import simplejson as json 9 | from common.config import SysConfig 10 | import logging 11 | 12 | logger = logging.getLogger('default') 13 | 14 | 15 | class Aliyun(object): 16 | def __init__(self): 17 | try: 18 | sys_config = SysConfig() 19 | ak = sys_config.get('aliyun_ak') 20 | secret = sys_config.get('aliyun_secret') 21 | self.clt = AcsClient(ak=ak, secret=secret) 22 | except Exception as m: 23 | raise Exception(f'阿里云认证失败:{m}{traceback.format_exc()}') 24 | 25 | def request_api(self, request, *values): 26 | if values: 27 | for value in values: 28 | for k, v in value.items(): 29 | request.add_query_param(k, v) 30 | request.set_accept_format('json') 31 | result = self.clt.do_action_with_exception(request) 32 | return json.dumps(json.loads(result.decode('utf-8')), indent=4, sort_keys=False, ensure_ascii=False) 33 | 34 | # 阿里云UTC时间转换为本地时区时间 35 | @staticmethod 36 | def utc2local(utc, utc_format): 37 | utc_time = datetime.datetime.strptime(utc, utc_format) 38 | local_tm = datetime.datetime.fromtimestamp(0) 39 | utc_tm = datetime.datetime.utcfromtimestamp(0) 40 | localtime = utc_time + (local_tm - utc_tm) 41 | return localtime 42 | 43 | def DescribeSlowLogs(self, DBInstanceId, StartTime, EndTime, **kwargs): 44 | """获取实例慢日志列表DBName,SortKey、PageSize、PageNumber""" 45 | request = DescribeSlowLogsRequest.DescribeSlowLogsRequest() 46 | values = {"action_name": "DescribeSlowLogs", "DBInstanceId": DBInstanceId, 47 | "StartTime": StartTime, "EndTime": EndTime, "SortKey": "TotalExecutionCounts"} 48 | values = dict(values, **kwargs) 49 | result = self.request_api(request, values) 50 | return result 51 | 52 | def DescribeSlowLogRecords(self, DBInstanceId, StartTime, EndTime, **kwargs): 53 | """查看慢日志明细SQLId,DBName、PageSize、PageNumber""" 54 | request = DescribeSlowLogRecordsRequest.DescribeSlowLogRecordsRequest() 55 | values = {"action_name": "DescribeSlowLogRecords", "DBInstanceId": DBInstanceId, 56 | "StartTime": StartTime, "EndTime": EndTime} 57 | values = dict(values, **kwargs) 58 | result = self.request_api(request, values) 59 | return result 60 | 61 | def RequestServiceOfCloudDBA(self, DBInstanceId, ServiceRequestType, ServiceRequestParam, **kwargs): 62 | """ 63 | 获取统计信息:'GetTimedMonData',{"Language":"zh","KeyGroup":"mem_cpu_usage","KeyName":"","StartTime":"2018-01-15T04:03:26Z","EndTime":"2018-01-15T05:03:26Z"} 64 | mem_cpu_usage、iops_usage、detailed_disk_space 65 | 获取process信息:'ShowProcessList',{"Language":"zh","Command":"Query"} -- Not Sleep , All 66 | 终止进程:'ConfirmKillSessionRequest',{"Language":"zh","SQLRequestID":75865,"SQLStatement":"kill 34022786;"} 67 | 获取表空间信息:'GetSpaceStatForTables',{"Language": "zh", "OrderType": "Data"} 68 | 获取资源利用信息:'GetResourceUsage',{"Language":"zh"} 69 | """ 70 | request = RequestServiceOfCloudDBARequest.RequestServiceOfCloudDBARequest() 71 | values = {"action_name": "RequestServiceOfCloudDBA", "DBInstanceId": DBInstanceId, 72 | "ServiceRequestType": ServiceRequestType, "ServiceRequestParam": ServiceRequestParam} 73 | values = dict(values, **kwargs) 74 | result = self.request_api(request, values) 75 | return result 76 | -------------------------------------------------------------------------------- /common/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | import logging 3 | import traceback 4 | 5 | import simplejson as json 6 | from django.http import HttpResponse 7 | 8 | from common.utils.permission import superuser_required 9 | from sql.models import Config 10 | from django.db import transaction 11 | from django.core.cache import cache 12 | 13 | logger = logging.getLogger('default') 14 | 15 | 16 | class SysConfig(object): 17 | def __init__(self): 18 | self.sys_config = {} 19 | self.get_all_config() 20 | 21 | def get_all_config(self): 22 | # 优先获取缓存数据 23 | try: 24 | sys_config = cache.get('sys_config') 25 | except Exception as m: 26 | sys_config = None 27 | logger.error(f"读取缓存失败:{m}{traceback.format_exc()}") 28 | 29 | # 缓存获取失败从数据库获取并且尝试更新缓存 30 | if sys_config: 31 | self.sys_config = sys_config 32 | else: 33 | try: 34 | # 获取系统配置信息 35 | all_config = Config.objects.all().values('item', 'value') 36 | sys_config = {} 37 | for items in all_config: 38 | if items['value'] in ('true', 'True'): 39 | items['value'] = True 40 | elif items['value'] in ('false', 'False'): 41 | items['value'] = False 42 | sys_config[items['item']] = items['value'] 43 | self.sys_config = sys_config 44 | except Exception as m: 45 | logger.error(f"获取系统配置信息失败:{m}{traceback.format_exc()}") 46 | self.sys_config = {} 47 | else: 48 | try: 49 | # 更新缓存 50 | cache.set('sys_config', self.sys_config, timeout=None) 51 | except Exception as m: 52 | logger.error(f"更新缓存失败:{m}{traceback.format_exc()}") 53 | 54 | def get(self, key, default_value=None): 55 | value = self.sys_config.get(key, default_value) 56 | # 是字符串的话, 如果是空, 或者全是空格, 返回默认值 57 | if isinstance(value, str) and value.strip() == '': 58 | return default_value 59 | return value 60 | 61 | def set(self, key, value): 62 | if value is True: 63 | value = 'true' 64 | elif value is False: 65 | value = 'false' 66 | config_item, created = Config.objects.get_or_create(item=key) 67 | config_item.value = value 68 | config_item.save() 69 | # 删除并更新缓存 70 | try: 71 | cache.delete('sys_config') 72 | except Exception as m: 73 | logger.error(f"删除缓存失败:{m}{traceback.format_exc()}") 74 | finally: 75 | self.get_all_config() 76 | 77 | def replace(self, configs): 78 | result = {'status': 0, 'msg': 'ok', 'data': []} 79 | 80 | # 清空并替换 81 | try: 82 | self.purge() 83 | with transaction.atomic(): 84 | Config.objects.bulk_create( 85 | [Config(item=items['key'].strip(), 86 | value=str(items['value']).strip()) for items in json.loads(configs)]) 87 | except Exception as e: 88 | logger.error(traceback.format_exc()) 89 | result['status'] = 1 90 | result['msg'] = str(e) 91 | finally: 92 | self.get_all_config() 93 | return result 94 | 95 | def purge(self): 96 | """清除所有配置, 供测试以及replace方法使用""" 97 | try: 98 | self.sys_config = {} 99 | cache.delete('sys_config') 100 | except Exception as m: 101 | logger.error(f"删除缓存失败:{m}{traceback.format_exc()}") 102 | with transaction.atomic(): 103 | Config.objects.all().delete() 104 | 105 | 106 | # 修改系统配置 107 | @superuser_required 108 | def change_config(request): 109 | configs = request.POST.get('configs') 110 | archer_config = SysConfig() 111 | result = archer_config.replace(configs) 112 | # 返回结果 113 | return HttpResponse(json.dumps(result), content_type='application/json') 114 | -------------------------------------------------------------------------------- /common/static/bootstrap-table/extensions/editable/bootstrap-table-editable.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bootstrap-table - An extended Bootstrap table with radio, checkbox, sort, pagination, and other added features. (supports twitter bootstrap v2 and v3). 3 | * 4 | * @version v1.14.2 5 | * @homepage https://bootstrap-table.com 6 | * @author wenzhixin (http://wenzhixin.net.cn/) 7 | * @license MIT 8 | */ 9 | 10 | (function(a,b){if('function'==typeof define&&define.amd)define([],b);else if('undefined'!=typeof exports)b();else{b(),a.bootstrapTableEditable={exports:{}}.exports}})(this,function(){'use strict';function a(a,b){if(!(a instanceof b))throw new TypeError('Cannot call a class as a function')}function b(a,b){if(!a)throw new ReferenceError('this hasn\'t been initialised - super() hasn\'t been called');return b&&('object'==typeof b||'function'==typeof b)?b:a}function c(a,b){if('function'!=typeof b&&null!==b)throw new TypeError('Super expression must either be null or a function, not '+typeof b);a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}}),b&&(Object.setPrototypeOf?Object.setPrototypeOf(a,b):a.__proto__=b)}var d=function(){function a(a,b){for(var c,d=0;d':l}}})}},{key:'initBody',value:function(a){var b=this;e(i.prototype.__proto__||Object.getPrototypeOf(i.prototype),'initBody',this).call(this,a),this.options.editable&&(f.each(this.columns,function(a,c){if(c.editable){var d=b.getData(),e=b.$body.find('a[data-name="'+c.field+'"]');e.each(function(a,b){var e=f(b),h=e.closest('tr'),i=h.data('index'),j=d[i],k=g.calculateObjectValue(c,c.editable,[i,j,e],{});e.editable(k)}),e.off('save').on('save',function(a,d){var e=a.currentTarget,g=d.submitValue,h=f(e),i=b.getData(),j=h.parents('tr[data-index]').data('index'),k=i[j],l=k[c.field];h.data('value',g),k[c.field]=g,b.trigger('editable-save',c.field,k,l,h),b.resetFooter()}),e.off('shown').on('shown',function(a,d){var e=a.currentTarget,g=f(e),h=b.getData(),i=g.parents('tr[data-index]').data('index'),j=h[i];b.trigger('editable-shown',c.field,j,g,d)}),e.off('hidden').on('hidden',function(a,d){var e=a.currentTarget,g=f(e),h=b.getData(),i=g.parents('tr[data-index]').data('index'),j=h[i];b.trigger('editable-hidden',c.field,j,g,d)})}}),this.trigger('editable-init'))}}]),i}(f.BootstrapTable)})(jQuery)}); -------------------------------------------------------------------------------- /common/static/bootstrap-fileinput/js/locales/zh.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * FileInput Chinese Translations 3 | * 4 | * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or 5 | * any HTML markup tags in the messages must not be converted or translated. 6 | * 7 | * @see http://github.com/kartik-v/bootstrap-fileinput 8 | * @author kangqf 9 | * 10 | * NOTE: this file must be saved in UTF-8 encoding. 11 | */ 12 | (function ($) { 13 | "use strict"; 14 | 15 | $.fn.fileinputLocales['zh'] = { 16 | fileSingle: '文件', 17 | filePlural: '个文件', 18 | browseLabel: '选择 …', 19 | removeLabel: '移除', 20 | removeTitle: '清除选中文件', 21 | cancelLabel: '取消', 22 | cancelTitle: '取消进行中的上传', 23 | uploadLabel: '上传', 24 | uploadTitle: '上传选中文件', 25 | msgNo: '没有', 26 | msgNoFilesSelected: '', 27 | msgCancelled: '取消', 28 | msgZoomModalHeading: '详细预览', 29 | msgFileRequired: '必须选择一个文件上传.', 30 | msgSizeTooSmall: '文件 "{name}" ({size} KB) 必须大于限定大小 {minSize} KB.', 31 | msgSizeTooLarge: '文件 "{name}" ({size} KB) 超过了允许大小 {maxSize} KB.', 32 | msgFilesTooLess: '你必须选择最少 {n} {files} 来上传. ', 33 | msgFilesTooMany: '选择的上传文件个数 ({n}) 超出最大文件的限制个数 {m}.', 34 | msgFileNotFound: '文件 "{name}" 未找到!', 35 | msgFileSecured: '安全限制,为了防止读取文件 "{name}".', 36 | msgFileNotReadable: '文件 "{name}" 不可读.', 37 | msgFilePreviewAborted: '取消 "{name}" 的预览.', 38 | msgFilePreviewError: '读取 "{name}" 时出现了一个错误.', 39 | msgInvalidFileName: '文件名 "{name}" 包含非法字符.', 40 | msgInvalidFileType: '不正确的类型 "{name}". 只支持 "{types}" 类型的文件.', 41 | msgInvalidFileExtension: '不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.', 42 | msgFileTypes: { 43 | 'image': 'image', 44 | 'html': 'HTML', 45 | 'text': 'text', 46 | 'video': 'video', 47 | 'audio': 'audio', 48 | 'flash': 'flash', 49 | 'pdf': 'PDF', 50 | 'object': 'object' 51 | }, 52 | msgUploadAborted: '该文件上传被中止', 53 | msgUploadThreshold: '处理中...', 54 | msgUploadBegin: '正在初始化...', 55 | msgUploadEnd: '完成', 56 | msgUploadEmpty: '无效的文件上传.', 57 | msgUploadError: 'Error', 58 | msgValidationError: '验证错误', 59 | msgLoading: '加载第 {index} 文件 共 {files} …', 60 | msgProgress: '加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.', 61 | msgSelected: '{n} {files} 选中', 62 | msgFoldersNotAllowed: '只支持拖拽文件! 跳过 {n} 拖拽的文件夹.', 63 | msgImageWidthSmall: '图像文件的"{name}"的宽度必须是至少{size}像素.', 64 | msgImageHeightSmall: '图像文件的"{name}"的高度必须至少为{size}像素.', 65 | msgImageWidthLarge: '图像文件"{name}"的宽度不能超过{size}像素.', 66 | msgImageHeightLarge: '图像文件"{name}"的高度不能超过{size}像素.', 67 | msgImageResizeError: '无法获取的图像尺寸调整。', 68 | msgImageResizeException: '调整图像大小时发生错误。
{errors}
', 69 | msgAjaxError: '{operation} 发生错误. 请重试!', 70 | msgAjaxProgressError: '{operation} 失败', 71 | ajaxOperations: { 72 | deleteThumb: '删除文件', 73 | uploadThumb: '上传文件', 74 | uploadBatch: '批量上传', 75 | uploadExtra: '表单数据上传' 76 | }, 77 | dropZoneTitle: '拖拽文件到这里 …
支持多文件同时上传', 78 | dropZoneClickTitle: '
(或点击{files}按钮选择文件)', 79 | fileActionSettings: { 80 | removeTitle: '删除文件', 81 | uploadTitle: '上传文件', 82 | uploadRetryTitle: 'Retry upload', 83 | zoomTitle: '查看详情', 84 | dragTitle: '移动 / 重置', 85 | indicatorNewTitle: '没有上传', 86 | indicatorSuccessTitle: '上传', 87 | indicatorErrorTitle: '上传错误', 88 | indicatorLoadingTitle: '上传 ...' 89 | }, 90 | previewZoomButtonTitles: { 91 | prev: '预览上一个文件', 92 | next: '预览下一个文件', 93 | toggleheader: '缩放', 94 | fullscreen: '全屏', 95 | borderless: '无边界模式', 96 | close: '关闭当前预览' 97 | } 98 | }; 99 | })(window.jQuery); 100 | -------------------------------------------------------------------------------- /sql/utils/sql_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @author: hhyo 4 | @license: Apache Licence 5 | @file: sql_utils.py 6 | @time: 2019/03/13 7 | """ 8 | import re 9 | import xml 10 | import mybatis_mapper2sql 11 | import sqlparse 12 | 13 | from sql.utils.extract_tables import extract_tables as extract_tables_by_sql_parse 14 | 15 | __author__ = 'hhyo' 16 | 17 | 18 | def get_syntax_type(sql, parser=True, db_type='mysql'): 19 | """ 20 | 返回SQL语句类型,仅判断DDL和DML 21 | :param sql: 22 | :param parser: 是否使用sqlparse解析 23 | :param db_type: 不使用sqlparse解析时需要提供该参数 24 | :return: 25 | """ 26 | sql = remove_comments(sql=sql, db_type=db_type) 27 | if parser: 28 | try: 29 | statement = sqlparse.parse(sql)[0] 30 | syntax_type = statement.token_first(skip_cm=True).ttype.__str__() 31 | if syntax_type == 'Token.Keyword.DDL': 32 | syntax_type = 'DDL' 33 | elif syntax_type == 'Token.Keyword.DML': 34 | syntax_type = 'DML' 35 | except Exception: 36 | syntax_type = None 37 | else: 38 | if db_type == 'mysql': 39 | ddl_re = r"^alter|^create|^drop|^rename|^truncate" 40 | dml_re = r"^call|^delete|^do|^handler|^insert|^load\s+data|^load\s+xml|^replace|^select|^update" 41 | else: 42 | # TODO 其他数据库的解析正则 43 | return None 44 | if re.match(ddl_re, sql, re.I): 45 | syntax_type = 'DDL' 46 | elif re.match(dml_re, sql, re.I): 47 | syntax_type = 'DML' 48 | else: 49 | syntax_type = None 50 | return syntax_type 51 | 52 | 53 | def remove_comments(sql, db_type='mysql'): 54 | """ 55 | 去除SQL语句中的注释信息 56 | 来源:https://stackoverflow.com/questions/35647841/parse-sql-file-with-comments-into-sqlite-with-python 57 | :param sql: 58 | :param db_type: 59 | :return: 60 | """ 61 | sql_comments_re = { 62 | 'oracle': 63 | [r'(?:--)[^\n]*\n', r'(?:\W|^)(?:remark|rem)\s+[^\n]*\n'], 64 | 'mysql': 65 | [r'(?:#|--\s)[^\n]*\n'] 66 | } 67 | specific_comment_re = sql_comments_re[db_type] 68 | additional_patterns = "|" 69 | if isinstance(specific_comment_re, str): 70 | additional_patterns += specific_comment_re 71 | elif isinstance(specific_comment_re, list): 72 | additional_patterns += "|".join(specific_comment_re) 73 | pattern = r"(\".*?\"|\'.*?\')|(/\*.*?\*/{})".format(additional_patterns) 74 | regex = re.compile(pattern, re.MULTILINE | re.DOTALL) 75 | 76 | def _replacer(match): 77 | if match.group(2): 78 | return "" 79 | else: 80 | return match.group(1) 81 | 82 | return regex.sub(_replacer, sql).strip() 83 | 84 | 85 | def extract_tables(sql): 86 | """ 87 | 获取sql语句中的库、表名 88 | :param sql: 89 | :return: 90 | """ 91 | tables = list() 92 | for i in extract_tables_by_sql_parse(sql): 93 | tables.append({ 94 | "schema": i.schema, 95 | "name": i.name, 96 | }) 97 | return tables 98 | 99 | 100 | def generate_sql(text): 101 | """ 102 | 从SQL文本、MyBatis3 Mapper XML file文件中解析出sql 列表 103 | :param text: 104 | :return: [{"sql_id": key, "sql": soar.compress(value)}] 105 | """ 106 | # 尝试XML解析 107 | try: 108 | mapper, xml_raw_text = mybatis_mapper2sql.create_mapper(xml_raw_text=text) 109 | statements = mybatis_mapper2sql.get_statement(mapper, result_type='list') 110 | rows = [] 111 | # 压缩SQL语句,方便展示 112 | for statement in statements: 113 | for key, value in statement.items(): 114 | row = {"sql_id": key, "sql": value} 115 | rows.append(row) 116 | except xml.etree.ElementTree.ParseError: 117 | # 删除注释语句 118 | text = sqlparse.format(text, strip_comments=True) 119 | statements = sqlparse.split(text) 120 | rows = [] 121 | num = 0 122 | for statement in statements: 123 | num = num + 1 124 | row = {"sql_id": num, "sql": statement} 125 | rows.append(row) 126 | return rows 127 | --------------------------------------------------------------------------------