diff --git a/common/templates/base.html b/common/templates/base.html
index 388703ed1e..b69b91873e 100644
--- a/common/templates/base.html
+++ b/common/templates/base.html
@@ -242,11 +242,24 @@
+
+
+
+ {% endif %}
+ {% if perms.sql.audit_user %}
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/sql/templates/audit_sqlworkflow.html b/sql/templates/audit_sqlworkflow.html
new file mode 100644
index 0000000000..05fd935f43
--- /dev/null
+++ b/sql/templates/audit_sqlworkflow.html
@@ -0,0 +1,350 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+
+
+
+
+
+{% endblock content %}
+{% block js %}
+ {% load static %}
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/sql/templates/sqlquery.html b/sql/templates/sqlquery.html
index 651cf5957f..1ae4328b71 100644
--- a/sql/templates/sqlquery.html
+++ b/sql/templates/sqlquery.html
@@ -848,6 +848,44 @@
收藏语句
}]
})
}
+ // 对下载的按钮附加其他动作 需要等渲染结束后才能绑定
+ setTimeout("actionAppend()",100)
+ }
+
+ // 通过前端元素查看确定要绑定事件的对象,有其他更好的实现方式?
+ function downloadAppendOpt(){
+ $(".export.btn-group.open:first").children(".dropdown-menu:first")[0].onclick=function(){
+ // 实际绑定的动作
+ $.ajax({
+ type: "post",
+ url: "/audit/input/",
+ dataType: "json",
+ data: {
+ action:"下载"
+ },
+ complete: function () {
+ console.log("回调触发")
+ },
+ success: function (data) {
+ console.log("回调成功")
+ console.log(data)
+ },
+ error: function (XMLHttpRequest, textStatus, errorThrown) {
+ console.log("回调失败")
+ }
+ });
+
+ }
+ }
+
+ // 对tab中的按钮添加事件,按钮触发下拉单,下拉单实际进行下载动作
+ function actionAppend(){
+ for (var i=0; i<$(".btn.btn-default.btn-undefined.dropdown-toggle").length; i++ ){
+ $(".btn.btn-default.btn-undefined.dropdown-toggle")[i].onclick=function(){
+ // 下拉单必须渲染后才能绑定事件 但不能太后绑定以免点击后已经关闭
+ setTimeout("downloadAppendOpt()",10)
+ }
+ }
}
//将数据通过ajax提交给后端进行检查
diff --git a/sql/tests.py b/sql/tests.py
index c283944e9d..a8aa6035a7 100644
--- a/sql/tests.py
+++ b/sql/tests.py
@@ -229,6 +229,18 @@ def test_audit(self):
r = self.client.get(f'/audit/', data=data)
self.assertEqual(r.status_code, 200)
+ def test_audit_sqlquery(self):
+ """测试audit_sqlquery页面"""
+ data = {}
+ r = self.client.get(f'/audit_sqlquery/', data=data)
+ self.assertEqual(r.status_code, 200)
+
+ def test_audit_sqlworkflow(self):
+ """测试audit_sqlworkflow页面"""
+ data = {}
+ r = self.client.get(f'/audit_sqlworkflow/', data=data)
+ self.assertEqual(r.status_code, 200)
+
def test_groupmgmt(self):
"""测试groupmgmt页面"""
data = {}
diff --git a/sql/urls.py b/sql/urls.py
index c21291484f..1fe968a9b9 100644
--- a/sql/urls.py
+++ b/sql/urls.py
@@ -57,9 +57,12 @@
path('archive/
/', views.archive_detail, name='archive_detail'),
path('config/', views.config),
path('audit/', views.audit),
+ path('audit_sqlquery/', views.audit_sqlquery),
+ path('audit_sqlworkflow/', views.audit_sqlworkflow),
path('authenticate/', auth.authenticate_entry),
path('sqlworkflow_list/', sql_workflow.sql_workflow_list),
+ path('sqlworkflow_list_audit/', sql_workflow.sql_workflow_list_audit),
path('sqlworkflow/detail_content/', sql_workflow.detail_content),
path('sqlworkflow/backup_sql/', sql_workflow.backup_sql),
path('simplecheck/', sql_workflow.check),
@@ -116,6 +119,7 @@
path('query/', query.query),
path('query/querylog/', query.querylog),
+ path('query/querylog_audit/', query.querylog_audit),
path('query/favorite/', query.favorite),
path('query/explain/', sql.sql_optimize.explain),
path('query/applylist/', sql.query_privileges.query_priv_apply_list),
@@ -154,4 +158,5 @@
path('4admin/sync_ding_user/', ding_api.sync_ding_user),
path('audit/log/', audit_log.audit_log),
+ path('audit/input/', audit_log.audit_input),
]
diff --git a/sql/views.py b/sql/views.py
index 4259f8eccd..d41a24e805 100644
--- a/sql/views.py
+++ b/sql/views.py
@@ -21,7 +21,7 @@
from sql.utils.tasks import task_info
from .models import Users, SqlWorkflow, QueryPrivileges, ResourceGroup, \
- QueryPrivilegesApply, Config, SQL_WORKFLOW_CHOICES, InstanceTag, Instance, QueryLog, ArchiveConfig
+ QueryPrivilegesApply, Config, SQL_WORKFLOW_CHOICES, InstanceTag, Instance, QueryLog, ArchiveConfig, AuditEntry
from sql.utils.workflow_audit import Audit
from sql.utils.sql_review import can_execute, can_timingtask, can_cancel, can_view, can_rollback
from common.utils.const import Const, WorkflowDict
@@ -57,7 +57,7 @@ def sqlworkflow(request):
# 过滤筛选项的数据
filter_dict = dict()
# 管理员,可查看所有工单
- if user.is_superuser:
+ if user.is_superuser or user.has_perm('sql.audit_user'):
pass
# 非管理员,拥有审核权限、资源组粒度执行权限的,可以查看组内所有工单
elif user.has_perm('sql.sql_review') or user.has_perm('sql.sql_execute_for_resource_group'):
@@ -435,7 +435,45 @@ def dbaprinciples(request):
return render(request, 'dbaprinciples.html', {'md': md})
-@superuser_required
+@permission_required('sql.audit_user', raise_exception=True)
def audit(request):
- """登录审计日志页面"""
- return render(request, 'audit.html')
+ """通用审计日志页面"""
+ _action_types = AuditEntry.objects.values_list('action').distinct()
+ action_types = [ i[0] for i in _action_types ]
+ return render(request, 'audit.html', {'action_types': action_types})
+
+
+@permission_required('sql.audit_user', raise_exception=True)
+def audit_sqlquery(request):
+ """SQL在线查询页面审计"""
+ user = request.user
+ favorites = QueryLog.objects.filter(username=user.username, favorite=True).values('id', 'alias')
+ is_download = 1 if user.has_perm('sql.download') or user.is_superuser else 0
+ return render(request, 'audit_sqlquery.html', {'favorites': favorites, 'is_download':is_download})
+
+
+def audit_sqlworkflow(request):
+ """SQL上线工单列表页面"""
+ user = request.user
+ # 过滤筛选项的数据
+ filter_dict = dict()
+ # 管理员,可查看所有工单
+ if user.is_superuser or user.has_perm('sql.audit_user'):
+ pass
+ # 非管理员,拥有审核权限、资源组粒度执行权限的,可以查看组内所有工单
+ elif user.has_perm('sql.sql_review') or user.has_perm('sql.sql_execute_for_resource_group'):
+ # 先获取用户所在资源组列表
+ group_list = user_groups(user)
+ group_ids = [group.group_id for group in group_list]
+ filter_dict['group_id__in'] = group_ids
+ # 其他人只能查看自己提交的工单
+ else:
+ filter_dict['engineer'] = user.username
+ instance_id = SqlWorkflow.objects.filter(**filter_dict).values('instance_id').distinct()
+ instance = Instance.objects.filter(pk__in=instance_id)
+ resource_group_id = SqlWorkflow.objects.filter(**filter_dict).values('group_id').distinct()
+ resource_group = ResourceGroup.objects.filter(group_id__in=resource_group_id)
+
+ return render(request, 'audit_sqlworkflow.html',
+ {'status_list': SQL_WORKFLOW_CHOICES,
+ 'instance': instance, 'resource_group': resource_group})
\ No newline at end of file
diff --git a/src/init_sql/v1.8.3.sql b/src/init_sql/v1.8.3.sql
index a0df1445f5..1217808cfe 100644
--- a/src/init_sql/v1.8.3.sql
+++ b/src/init_sql/v1.8.3.sql
@@ -15,4 +15,12 @@ set @content_type_id=(select id from django_content_type where app_label='sql' a
INSERT INTO auth_permission (name, content_type_id, codename) VALUES ('菜单 My2SQL', @content_type_id, 'menu_my2sql');
-- ssh 隧道功能修改
-ALTER TABLE `ssh_tunnel` ADD COLUMN pkey longtext NULL AFTER password;
\ No newline at end of file
+ALTER TABLE `ssh_tunnel` ADD COLUMN pkey longtext NULL AFTER password;
+
+-- 审计功能增强
+alter table audit_log change `ip` `extra_info` varchar(255) DEFAULT NULL COMMENT '额外的信息';
+alter table audit_log add `user_display` varchar(50) DEFAULT NULL COMMENT '用户中文名';
+
+set @content_type_id=(select id from django_content_type where app_label='sql' and model='permission');
+insert IGNORE INTO auth_permission (name, content_type_id, codename) VALUES
+('审计权限 ', @content_type_id, 'audit_user');