├── 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 |
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 |
--------------------------------------------------------------------------------