mirror of
https://github.com/opsre/LiteOps.git
synced 2026-03-02 12:00:45 +08:00
✨ feat: 清理构建日志及登陆功能
This commit is contained in:
@@ -4,9 +4,13 @@ from django.http import JsonResponse
|
||||
from django.views import View
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.db import transaction
|
||||
from ..models import SecurityConfig, User
|
||||
from django.utils import timezone
|
||||
from datetime import datetime, timedelta
|
||||
from ..models import SecurityConfig, User, BuildTask, BuildHistory, LoginLog
|
||||
from ..utils.auth import jwt_auth_required
|
||||
from ..utils.permissions import get_user_permissions
|
||||
|
||||
logger = logging.getLogger('apps')
|
||||
|
||||
@@ -140,4 +144,227 @@ class SecurityConfigView(View):
|
||||
return JsonResponse({
|
||||
'code': 500,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@jwt_auth_required
|
||||
@require_http_methods(["GET"])
|
||||
def get_build_tasks_for_cleanup(request):
|
||||
"""获取可用于日志清理的构建任务列表"""
|
||||
try:
|
||||
# 获取用户权限信息
|
||||
user_permissions = get_user_permissions(request.user_id)
|
||||
data_permissions = user_permissions.get('data', {})
|
||||
|
||||
# 检查用户是否有系统基本设置权限
|
||||
function_permissions = user_permissions.get('function', {})
|
||||
system_permissions = function_permissions.get('system_basic', [])
|
||||
|
||||
if 'view' not in system_permissions:
|
||||
logger.warning(f'用户[{request.user_id}]没有系统基本设置查看权限')
|
||||
return JsonResponse({
|
||||
'code': 403,
|
||||
'message': '没有权限查看构建任务'
|
||||
}, status=403)
|
||||
|
||||
# 应用项目权限过滤
|
||||
project_scope = data_permissions.get('project_scope', 'all')
|
||||
if project_scope == 'custom':
|
||||
permitted_project_ids = data_permissions.get('project_ids', [])
|
||||
if not permitted_project_ids:
|
||||
return JsonResponse({
|
||||
'code': 200,
|
||||
'message': '获取构建任务列表成功',
|
||||
'data': []
|
||||
})
|
||||
tasks = BuildTask.objects.filter(project__project_id__in=permitted_project_ids).select_related('project')
|
||||
else:
|
||||
tasks = BuildTask.objects.all().select_related('project')
|
||||
|
||||
# 格式化返回数据
|
||||
task_list = []
|
||||
for task in tasks:
|
||||
task_list.append({
|
||||
'task_id': task.task_id,
|
||||
'name': task.name,
|
||||
'project_name': task.project.name if task.project else '未知项目',
|
||||
'total_builds': task.total_builds
|
||||
})
|
||||
|
||||
return JsonResponse({
|
||||
'code': 200,
|
||||
'message': '获取构建任务列表成功',
|
||||
'data': task_list
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'获取构建任务列表失败: {str(e)}', exc_info=True)
|
||||
return JsonResponse({
|
||||
'code': 500,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
})
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@jwt_auth_required
|
||||
@require_http_methods(["POST"])
|
||||
def cleanup_build_logs(request):
|
||||
"""清理构建日志"""
|
||||
try:
|
||||
# 获取用户权限信息
|
||||
user_permissions = get_user_permissions(request.user_id)
|
||||
|
||||
# 检查用户是否有系统基本设置编辑权限
|
||||
function_permissions = user_permissions.get('function', {})
|
||||
system_permissions = function_permissions.get('system_basic', [])
|
||||
|
||||
if 'edit' not in system_permissions:
|
||||
logger.warning(f'用户[{request.user_id}]没有系统基本设置编辑权限')
|
||||
return JsonResponse({
|
||||
'code': 403,
|
||||
'message': '没有权限执行日志清理操作'
|
||||
}, status=403)
|
||||
|
||||
data = json.loads(request.body)
|
||||
task_ids = data.get('task_ids', [])
|
||||
days_before = data.get('days_before', 30)
|
||||
|
||||
if not isinstance(days_before, int) or days_before < 1 or days_before > 365:
|
||||
return JsonResponse({
|
||||
'code': 400,
|
||||
'message': '保留天数必须在1-365天之间'
|
||||
})
|
||||
|
||||
# 计算截止日期
|
||||
cutoff_date = timezone.now() - timedelta(days=days_before)
|
||||
|
||||
# 查询要删除的构建历史记录
|
||||
if task_ids:
|
||||
# 清理指定任务的日志
|
||||
histories_to_delete = BuildHistory.objects.filter(
|
||||
task__task_id__in=task_ids,
|
||||
create_time__lt=cutoff_date
|
||||
)
|
||||
else:
|
||||
# 清理所有任务的日志
|
||||
histories_to_delete = BuildHistory.objects.filter(
|
||||
create_time__lt=cutoff_date
|
||||
)
|
||||
|
||||
# 统计信息
|
||||
total_count = histories_to_delete.count()
|
||||
|
||||
if total_count == 0:
|
||||
task_desc = f"{len(task_ids)}个指定任务" if task_ids else "所有任务"
|
||||
return JsonResponse({
|
||||
'code': 200,
|
||||
'message': f'没有找到{task_desc}中需要清理的构建日志记录',
|
||||
'data': {
|
||||
'deleted_count': 0,
|
||||
'task_count': len(task_ids) if task_ids else 0,
|
||||
'days_before': days_before
|
||||
}
|
||||
})
|
||||
|
||||
# 执行删除操作
|
||||
with transaction.atomic():
|
||||
deleted_count, _ = histories_to_delete.delete()
|
||||
|
||||
# 记录操作日志
|
||||
user = User.objects.get(user_id=request.user_id)
|
||||
task_desc = f"{len(task_ids)}个指定任务" if task_ids else "所有任务"
|
||||
logger.info(f'用户[{user.username}]清理了{task_desc}{days_before}天前的构建日志,共删除{deleted_count}条记录')
|
||||
|
||||
return JsonResponse({
|
||||
'code': 200,
|
||||
'message': f'构建日志清理完成,共删除{deleted_count}条记录',
|
||||
'data': {
|
||||
'deleted_count': deleted_count,
|
||||
'task_count': len(task_ids) if task_ids else 0,
|
||||
'days_before': days_before
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'清理构建日志失败: {str(e)}', exc_info=True)
|
||||
return JsonResponse({
|
||||
'code': 500,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
})
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@jwt_auth_required
|
||||
@require_http_methods(["POST"])
|
||||
def cleanup_login_logs(request):
|
||||
"""清理登录日志"""
|
||||
try:
|
||||
# 获取用户权限信息
|
||||
user_permissions = get_user_permissions(request.user_id)
|
||||
|
||||
# 检查用户是否有系统基本设置编辑权限
|
||||
function_permissions = user_permissions.get('function', {})
|
||||
system_permissions = function_permissions.get('system_basic', [])
|
||||
|
||||
if 'edit' not in system_permissions:
|
||||
logger.warning(f'用户[{request.user_id}]没有系统基本设置编辑权限')
|
||||
return JsonResponse({
|
||||
'code': 403,
|
||||
'message': '没有权限执行日志清理操作'
|
||||
}, status=403)
|
||||
|
||||
data = json.loads(request.body)
|
||||
days_before = data.get('days_before', 30)
|
||||
|
||||
# 验证输入参数
|
||||
if not isinstance(days_before, int) or days_before < 1 or days_before > 365:
|
||||
return JsonResponse({
|
||||
'code': 400,
|
||||
'message': '保留天数必须在1-365天之间'
|
||||
})
|
||||
|
||||
# 计算截止日期
|
||||
cutoff_date = timezone.now() - timedelta(days=days_before)
|
||||
|
||||
# 查询要删除的登录日志记录
|
||||
logs_to_delete = LoginLog.objects.filter(
|
||||
login_time__lt=cutoff_date
|
||||
)
|
||||
|
||||
# 统计信息
|
||||
total_count = logs_to_delete.count()
|
||||
|
||||
if total_count == 0:
|
||||
return JsonResponse({
|
||||
'code': 200,
|
||||
'message': '没有找到需要清理的登录日志记录',
|
||||
'data': {
|
||||
'deleted_count': 0,
|
||||
'days_before': days_before
|
||||
}
|
||||
})
|
||||
|
||||
# 执行删除操作
|
||||
with transaction.atomic():
|
||||
deleted_count, _ = logs_to_delete.delete()
|
||||
|
||||
# 记录操作日志
|
||||
user = User.objects.get(user_id=request.user_id)
|
||||
logger.info(f'用户[{user.username}]清理了{days_before}天前的登录日志,共删除{deleted_count}条记录')
|
||||
|
||||
return JsonResponse({
|
||||
'code': 200,
|
||||
'message': f'登录日志清理完成,共删除{deleted_count}条记录',
|
||||
'data': {
|
||||
'deleted_count': deleted_count,
|
||||
'days_before': days_before
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'清理登录日志失败: {str(e)}', exc_info=True)
|
||||
return JsonResponse({
|
||||
'code': 500,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
})
|
||||
@@ -15,7 +15,7 @@ from apps.views.logs import login_logs_list, login_log_detail
|
||||
from apps.views.dashboard import DashboardStatsView, BuildTrendView, BuildDetailView, RecentBuildsView, ProjectDistributionView
|
||||
from apps.views.webhook import GitLabWebhookView
|
||||
|
||||
from apps.views.security import SecurityConfigView
|
||||
from apps.views.security import SecurityConfigView, get_build_tasks_for_cleanup, cleanup_build_logs, cleanup_login_logs
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
@@ -68,4 +68,7 @@ urlpatterns = [
|
||||
|
||||
# 安全配置相关路由
|
||||
path('api/system/security/', SecurityConfigView.as_view(), name='security-config'),
|
||||
path('api/system/security/build-tasks/', get_build_tasks_for_cleanup, name='build-tasks-for-cleanup'),
|
||||
path('api/system/security/cleanup-build-logs/', cleanup_build_logs, name='cleanup-build-logs'),
|
||||
path('api/system/security/cleanup-login-logs/', cleanup_login_logs, name='cleanup-login-logs'),
|
||||
]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<a-tabs v-model:activeKey="activeTabKey" @change="handleTabChange">
|
||||
<!-- 安全设置 -->
|
||||
<a-tab-pane key="security-config" tab="安全设置">
|
||||
<a-tab-pane key="security-config" tab="设置">
|
||||
<a-card>
|
||||
<a-form
|
||||
ref="securityFormRef"
|
||||
@@ -83,9 +83,9 @@
|
||||
</a-row>
|
||||
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="saveSecurityConfig"
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="saveSecurityConfig"
|
||||
:loading="securityLoading"
|
||||
v-if="hasFunctionPermission('system_basic', 'edit')"
|
||||
>
|
||||
@@ -93,11 +93,99 @@
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<a-divider>日志清理</a-divider>
|
||||
|
||||
<!-- 日志清理功能 -->
|
||||
<a-form layout="vertical" v-if="hasFunctionPermission('system_basic', 'edit')">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="日志类型">
|
||||
<a-select
|
||||
v-model:value="logCleanupForm.logType"
|
||||
placeholder="请选择日志类型"
|
||||
style="width: 100%"
|
||||
@change="handleLogTypeChange"
|
||||
>
|
||||
<a-select-option value="build">构建日志</a-select-option>
|
||||
<a-select-option value="login">登录日志</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="保留天数">
|
||||
<a-input-number
|
||||
v-model:value="logCleanupForm.daysBefore"
|
||||
:min="1"
|
||||
:max="365"
|
||||
style="width: 100%"
|
||||
placeholder="请输入保留天数"
|
||||
addon-after="天"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="操作">
|
||||
<a-button
|
||||
type="primary"
|
||||
danger
|
||||
@click="handleLogCleanup"
|
||||
:loading="logCleanupLoading"
|
||||
:disabled="!logCleanupForm.logType || !logCleanupForm.daysBefore"
|
||||
style="width: 100%"
|
||||
>
|
||||
清理日志
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24" v-if="logCleanupForm.logType === 'build'">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="选择构建任务">
|
||||
<a-select
|
||||
v-model:value="logCleanupForm.selectedTasks"
|
||||
mode="multiple"
|
||||
placeholder="请选择要清理日志的构建任务,不选择则清理所有任务"
|
||||
style="width: 100%"
|
||||
:loading="buildTasksLoading"
|
||||
show-search
|
||||
:filter-option="filterBuildTasks"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option
|
||||
v-for="task in buildTasksList"
|
||||
:key="task.task_id"
|
||||
:value="task.task_id"
|
||||
>
|
||||
{{ task.name }} ({{ task.project_name }})
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<div class="form-item-help">
|
||||
将删除{{ logCleanupForm.selectedTasks.length > 0 ? '选定任务' : '所有任务' }}中超过{{ logCleanupForm.daysBefore || 0 }}天的构建日志
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24" v-if="logCleanupForm.logType === 'login'">
|
||||
<a-col :span="24">
|
||||
<a-alert
|
||||
message="登录日志清理说明"
|
||||
:description="`将删除超过${logCleanupForm.daysBefore || 0}天的所有登录日志记录,包括成功和失败的登录记录。`"
|
||||
type="info"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 通知配置 -->
|
||||
<a-tab-pane key="notification-config" tab="通知配置">
|
||||
<a-tab-pane key="notification-config" tab="通知">
|
||||
<a-card>
|
||||
<div class="notification-header">
|
||||
<a-button type="primary" @click="showAddRobot" v-if="hasFunctionPermission('system_basic', 'create')">
|
||||
@@ -288,7 +376,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import axios from 'axios';
|
||||
import { hasFunctionPermission, checkPermission } from '../../utils/permission';
|
||||
@@ -315,6 +403,16 @@ const securityRules = {
|
||||
]
|
||||
};
|
||||
|
||||
// 日志清理相关
|
||||
const buildTasksList = ref([]);
|
||||
const buildTasksLoading = ref(false);
|
||||
const logCleanupLoading = ref(false);
|
||||
const logCleanupForm = reactive({
|
||||
logType: 'build',
|
||||
selectedTasks: [],
|
||||
daysBefore: 30
|
||||
});
|
||||
|
||||
// 通知机器人相关
|
||||
const drawerVisible = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
@@ -388,6 +486,8 @@ const securityLoading = ref(false);
|
||||
const handleTabChange = (key) => {
|
||||
if (key === 'notification-config') {
|
||||
loadRobotList();
|
||||
} else if (key === 'security-config') {
|
||||
loadBuildTasksList();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -605,8 +705,114 @@ const handleDeleteRobot = async (robot) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 获取构建任务列表
|
||||
const loadBuildTasksList = async () => {
|
||||
try {
|
||||
buildTasksLoading.value = true;
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await axios.get('/api/system/security/build-tasks/', {
|
||||
headers: { 'Authorization': token }
|
||||
});
|
||||
|
||||
if (response.data.code === 200) {
|
||||
buildTasksList.value = response.data.data;
|
||||
} else {
|
||||
message.error(response.data.message || '获取构建任务列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Load build tasks error:', error);
|
||||
message.error('获取构建任务列表失败');
|
||||
} finally {
|
||||
buildTasksLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 构建任务过滤函数
|
||||
const filterBuildTasks = (input, option) => {
|
||||
return option.children[0].children.toLowerCase().includes(input.toLowerCase());
|
||||
};
|
||||
|
||||
// 日志类型变化处理
|
||||
const handleLogTypeChange = () => {
|
||||
// 切换日志类型清空构建任务选择
|
||||
logCleanupForm.selectedTasks = [];
|
||||
};
|
||||
|
||||
// 处理日志清理
|
||||
const handleLogCleanup = async () => {
|
||||
try {
|
||||
// 生成确认信息
|
||||
let confirmContent = '';
|
||||
if (logCleanupForm.logType === 'build') {
|
||||
const taskCount = logCleanupForm.selectedTasks.length;
|
||||
if (taskCount > 0) {
|
||||
confirmContent = `确定要清理选定的${taskCount}个构建任务中${logCleanupForm.daysBefore}天前的构建日志吗?`;
|
||||
} else {
|
||||
confirmContent = `确定要清理所有构建任务中${logCleanupForm.daysBefore}天前的构建日志吗?`;
|
||||
}
|
||||
} else if (logCleanupForm.logType === 'login') {
|
||||
confirmContent = `确定要清理${logCleanupForm.daysBefore}天前的所有登录日志吗?`;
|
||||
}
|
||||
confirmContent += '此操作不可恢复。';
|
||||
|
||||
// 确认对话框
|
||||
const confirmed = await new Promise((resolve) => {
|
||||
Modal.confirm({
|
||||
title: '确认清理日志',
|
||||
content: confirmContent,
|
||||
okText: '确定清理',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: () => resolve(true),
|
||||
onCancel: () => resolve(false)
|
||||
});
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
logCleanupLoading.value = true;
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
// 根据日志类型调用不同的API
|
||||
let apiUrl = '';
|
||||
let requestData = {};
|
||||
|
||||
if (logCleanupForm.logType === 'build') {
|
||||
apiUrl = '/api/system/security/cleanup-build-logs/';
|
||||
requestData = {
|
||||
task_ids: logCleanupForm.selectedTasks,
|
||||
days_before: logCleanupForm.daysBefore
|
||||
};
|
||||
} else if (logCleanupForm.logType === 'login') {
|
||||
apiUrl = '/api/system/security/cleanup-login-logs/';
|
||||
requestData = {
|
||||
days_before: logCleanupForm.daysBefore
|
||||
};
|
||||
}
|
||||
|
||||
const response = await axios.post(apiUrl, requestData, {
|
||||
headers: { 'Authorization': token }
|
||||
});
|
||||
|
||||
if (response.data.code === 200) {
|
||||
message.success(response.data.message);
|
||||
if (logCleanupForm.logType === 'build') {
|
||||
logCleanupForm.selectedTasks = [];
|
||||
}
|
||||
} else {
|
||||
message.error(response.data.message || '日志清理失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Cleanup logs error:', error);
|
||||
message.error('日志清理失败');
|
||||
} finally {
|
||||
logCleanupLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchSecurityConfig();
|
||||
loadBuildTasksList();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user