feat: add admin management, change password, and operation log
- Change password: current user can change their own password - Admin management: superuser can create/toggle/reset-password for admins - Operation log: view all system operations with type filter - All operations are recorded to AlertRecord for audit trail Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a7e030dc57
commit
cbc19a6d9e
@ -10,3 +10,17 @@ class UserInfoSerializer(serializers.Serializer):
|
|||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
username = serializers.CharField()
|
username = serializers.CharField()
|
||||||
is_superuser = serializers.BooleanField()
|
is_superuser = serializers.BooleanField()
|
||||||
|
is_active = serializers.BooleanField()
|
||||||
|
date_joined = serializers.DateTimeField()
|
||||||
|
last_login = serializers.DateTimeField()
|
||||||
|
|
||||||
|
|
||||||
|
class ChangePasswordSerializer(serializers.Serializer):
|
||||||
|
old_password = serializers.CharField(write_only=True)
|
||||||
|
new_password = serializers.CharField(write_only=True, min_length=6)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminUserCreateSerializer(serializers.Serializer):
|
||||||
|
username = serializers.CharField(max_length=150)
|
||||||
|
password = serializers.CharField(write_only=True, min_length=6)
|
||||||
|
is_superuser = serializers.BooleanField(default=False)
|
||||||
|
|||||||
@ -5,4 +5,9 @@ urlpatterns = [
|
|||||||
path('login/', views.login_view),
|
path('login/', views.login_view),
|
||||||
path('refresh/', views.refresh_view),
|
path('refresh/', views.refresh_view),
|
||||||
path('me/', views.me_view),
|
path('me/', views.me_view),
|
||||||
|
path('change-password/', views.change_password_view),
|
||||||
|
path('admins/', views.admin_list_view),
|
||||||
|
path('admins/create/', views.admin_create_view),
|
||||||
|
path('admins/<int:pk>/toggle/', views.admin_toggle_view),
|
||||||
|
path('admins/<int:pk>/reset-password/', views.admin_reset_password_view),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -5,7 +5,11 @@ from rest_framework.permissions import AllowAny, IsAuthenticated
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework_simplejwt.tokens import RefreshToken
|
from rest_framework_simplejwt.tokens import RefreshToken
|
||||||
|
|
||||||
from .serializers import LoginSerializer, UserInfoSerializer
|
from .models import AdminUser
|
||||||
|
from .serializers import (
|
||||||
|
LoginSerializer, UserInfoSerializer,
|
||||||
|
ChangePasswordSerializer, AdminUserCreateSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
@ -58,3 +62,136 @@ def refresh_view(request):
|
|||||||
@api_view(['GET'])
|
@api_view(['GET'])
|
||||||
def me_view(request):
|
def me_view(request):
|
||||||
return Response(UserInfoSerializer(request.user).data)
|
return Response(UserInfoSerializer(request.user).data)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
def change_password_view(request):
|
||||||
|
"""修改当前用户密码"""
|
||||||
|
serializer = ChangePasswordSerializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
if not request.user.check_password(serializer.validated_data['old_password']):
|
||||||
|
return Response({'error': 'wrong_password', 'message': '原密码错误'},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
request.user.set_password(serializer.validated_data['new_password'])
|
||||||
|
request.user.save()
|
||||||
|
|
||||||
|
# Log operation
|
||||||
|
from apps.monitor.models import AlertRecord
|
||||||
|
AlertRecord.objects.create(
|
||||||
|
alert_type=AlertRecord.AlertType.MANUAL,
|
||||||
|
title=f"管理员 {request.user.username} 修改密码",
|
||||||
|
content=f"操作人: {request.user.username}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response({'message': '密码修改成功,请重新登录'})
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== Admin User Management ====================
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
def admin_list_view(request):
|
||||||
|
"""列出所有管理员"""
|
||||||
|
if not request.user.is_superuser:
|
||||||
|
return Response({'error': 'forbidden', 'message': '仅超级管理员可操作'},
|
||||||
|
status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
users = AdminUser.objects.all().order_by('id')
|
||||||
|
return Response(UserInfoSerializer(users, many=True).data)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
def admin_create_view(request):
|
||||||
|
"""创建管理员账号"""
|
||||||
|
if not request.user.is_superuser:
|
||||||
|
return Response({'error': 'forbidden', 'message': '仅超级管理员可操作'},
|
||||||
|
status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
serializer = AdminUserCreateSerializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
d = serializer.validated_data
|
||||||
|
|
||||||
|
if AdminUser.objects.filter(username=d['username']).exists():
|
||||||
|
return Response({'error': 'user_exists', 'message': f'用户名 {d["username"]} 已存在'},
|
||||||
|
status=status.HTTP_409_CONFLICT)
|
||||||
|
|
||||||
|
user = AdminUser.objects.create_user(
|
||||||
|
username=d['username'],
|
||||||
|
password=d['password'],
|
||||||
|
is_superuser=d.get('is_superuser', False),
|
||||||
|
is_staff=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
from apps.monitor.models import AlertRecord
|
||||||
|
AlertRecord.objects.create(
|
||||||
|
alert_type=AlertRecord.AlertType.MANUAL,
|
||||||
|
title=f"创建管理员 {d['username']}",
|
||||||
|
content=f"操作人: {request.user.username},超级管理员: {'是' if d.get('is_superuser') else '否'}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
'message': f'管理员 {d["username"]} 创建成功',
|
||||||
|
'user': UserInfoSerializer(user).data,
|
||||||
|
}, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
def admin_toggle_view(request, pk):
|
||||||
|
"""启用/停用管理员"""
|
||||||
|
if not request.user.is_superuser:
|
||||||
|
return Response({'error': 'forbidden', 'message': '仅超级管理员可操作'},
|
||||||
|
status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = AdminUser.objects.get(pk=pk)
|
||||||
|
except AdminUser.DoesNotExist:
|
||||||
|
return Response({'error': 'not_found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
if user.pk == request.user.pk:
|
||||||
|
return Response({'error': 'self_toggle', 'message': '不能停用自己'},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
user.is_active = not user.is_active
|
||||||
|
user.save(update_fields=['is_active'])
|
||||||
|
|
||||||
|
action = '启用' if user.is_active else '停用'
|
||||||
|
from apps.monitor.models import AlertRecord
|
||||||
|
AlertRecord.objects.create(
|
||||||
|
alert_type=AlertRecord.AlertType.MANUAL,
|
||||||
|
title=f"{action}管理员 {user.username}",
|
||||||
|
content=f"操作人: {request.user.username}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response({'message': f'已{action}管理员 {user.username}',
|
||||||
|
'user': UserInfoSerializer(user).data})
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
def admin_reset_password_view(request, pk):
|
||||||
|
"""超管重置其他管理员密码"""
|
||||||
|
if not request.user.is_superuser:
|
||||||
|
return Response({'error': 'forbidden', 'message': '仅超级管理员可操作'},
|
||||||
|
status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = AdminUser.objects.get(pk=pk)
|
||||||
|
except AdminUser.DoesNotExist:
|
||||||
|
return Response({'error': 'not_found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
new_password = request.data.get('new_password', '')
|
||||||
|
if len(new_password) < 6:
|
||||||
|
return Response({'error': 'weak_password', 'message': '密码至少6位'},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
user.set_password(new_password)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
from apps.monitor.models import AlertRecord
|
||||||
|
AlertRecord.objects.create(
|
||||||
|
alert_type=AlertRecord.AlertType.MANUAL,
|
||||||
|
title=f"重置管理员 {user.username} 密码",
|
||||||
|
content=f"操作人: {request.user.username}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response({'message': f'已重置 {user.username} 的密码'})
|
||||||
|
|||||||
@ -24,6 +24,10 @@
|
|||||||
<el-icon><Setting /></el-icon>
|
<el-icon><Setting /></el-icon>
|
||||||
<span>系统设置</span>
|
<span>系统设置</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
<el-menu-item index="/admin">
|
||||||
|
<el-icon><Key /></el-icon>
|
||||||
|
<span>系统管理</span>
|
||||||
|
</el-menu-item>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ const routes = [
|
|||||||
{ path: 'billing', name: 'Billing', component: () => import('../views/billing/BillingView.vue') },
|
{ path: 'billing', name: 'Billing', component: () => import('../views/billing/BillingView.vue') },
|
||||||
{ path: 'alerts', name: 'Alerts', component: () => import('../views/alerts/AlertList.vue') },
|
{ path: 'alerts', name: 'Alerts', component: () => import('../views/alerts/AlertList.vue') },
|
||||||
{ path: 'settings', name: 'Settings', component: () => import('../views/settings/SettingsView.vue') },
|
{ path: 'settings', name: 'Settings', component: () => import('../views/settings/SettingsView.vue') },
|
||||||
|
{ path: 'admin', name: 'Admin', component: () => import('../views/admin/AdminView.vue') },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
307
frontend/src/views/admin/AdminView.vue
Normal file
307
frontend/src/views/admin/AdminView.vue
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
<template>
|
||||||
|
<div style="max-width: 1400px; margin: 0 auto;">
|
||||||
|
<el-tabs v-model="activeTab">
|
||||||
|
<!-- Tab 1: Change Password -->
|
||||||
|
<el-tab-pane label="修改密码" name="password">
|
||||||
|
<el-card style="max-width: 500px;">
|
||||||
|
<template #header>修改密码</template>
|
||||||
|
<el-form label-width="100px">
|
||||||
|
<el-form-item label="原密码">
|
||||||
|
<el-input v-model="pwdForm.old_password" type="password" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="新密码">
|
||||||
|
<el-input v-model="pwdForm.new_password" type="password" show-password
|
||||||
|
placeholder="至少6位" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="确认新密码">
|
||||||
|
<el-input v-model="pwdForm.confirm_password" type="password" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleChangePassword" :loading="pwdLoading">
|
||||||
|
修改密码
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<!-- Tab 2: Admin Management -->
|
||||||
|
<el-tab-pane label="管理员管理" name="admins" v-if="auth.user?.is_superuser">
|
||||||
|
<div style="margin-bottom: 16px;">
|
||||||
|
<el-button type="primary" @click="showCreateAdmin = true">创建管理员</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table :data="admins" stripe v-loading="adminsLoading" style="width: 100%;">
|
||||||
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
|
<el-table-column prop="username" label="用户名" min-width="150" />
|
||||||
|
<el-table-column label="角色" min-width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.is_superuser ? 'danger' : 'info'" size="small">
|
||||||
|
{{ row.is_superuser ? '超级管理员' : '普通管理员' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="状态" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.is_active ? 'success' : 'danger'" size="small">
|
||||||
|
{{ row.is_active ? '启用' : '停用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="最近登录" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.last_login ? new Date(row.last_login).toLocaleString('zh-CN') : '从未登录' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<template v-if="row.id !== auth.user?.id">
|
||||||
|
<el-button size="small" text @click="handleToggleAdmin(row)">
|
||||||
|
{{ row.is_active ? '停用' : '启用' }}
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" text type="warning" @click="openResetPwd(row)">
|
||||||
|
重置密码
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<span v-else style="color: #999; font-size: 12px;">当前账号</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<!-- Tab 3: Operation Log -->
|
||||||
|
<el-tab-pane label="操作日志" name="logs">
|
||||||
|
<div style="margin-bottom: 12px; display: flex; gap: 8px; align-items: center;">
|
||||||
|
<el-select v-model="logFilter" placeholder="全部类型" clearable style="width: 160px;">
|
||||||
|
<el-option label="手动操作" value="manual" />
|
||||||
|
<el-option label="告警" value="warning" />
|
||||||
|
<el-option label="自动停用" value="disable" />
|
||||||
|
<el-option label="错误" value="error" />
|
||||||
|
</el-select>
|
||||||
|
<el-input-number v-model="logLimit" :min="10" :max="500" :step="50"
|
||||||
|
style="width: 140px;" />
|
||||||
|
<el-button @click="loadLogs" :loading="logsLoading">刷新</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table :data="logs" stripe v-loading="logsLoading" style="width: 100%;"
|
||||||
|
empty-text="暂无日志记录">
|
||||||
|
<el-table-column label="时间" width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ new Date(row.created_at).toLocaleString('zh-CN') }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="类型" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="logTagType(row.alert_type)" size="small">
|
||||||
|
{{ logTypeLabel(row.alert_type) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="关联用户" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.iam_user_username || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="title" label="标题" min-width="250" />
|
||||||
|
<el-table-column prop="content" label="详情" min-width="300" show-overflow-tooltip />
|
||||||
|
</el-table>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
|
<!-- Create Admin Dialog -->
|
||||||
|
<el-dialog v-model="showCreateAdmin" title="创建管理员" width="90%" style="max-width: 450px;">
|
||||||
|
<el-form label-width="100px">
|
||||||
|
<el-form-item label="用户名">
|
||||||
|
<el-input v-model="createAdminForm.username" placeholder="英文字母开头" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="密码">
|
||||||
|
<el-input v-model="createAdminForm.password" type="password" show-password
|
||||||
|
placeholder="至少6位" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="超级管理员">
|
||||||
|
<el-switch v-model="createAdminForm.is_superuser" />
|
||||||
|
<span style="margin-left: 8px; font-size: 12px; color: #999;">
|
||||||
|
超级管理员可以管理其他管理员账号
|
||||||
|
</span>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showCreateAdmin = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleCreateAdmin" :loading="createAdminLoading">创建</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- Reset Password Dialog -->
|
||||||
|
<el-dialog v-model="showResetPwd" title="重置密码" width="90%" style="max-width: 400px;">
|
||||||
|
<p style="margin-bottom: 12px; color: #606266;">
|
||||||
|
重置 <strong>{{ resetPwdUser?.username }}</strong> 的密码
|
||||||
|
</p>
|
||||||
|
<el-input v-model="resetPwdValue" type="password" show-password
|
||||||
|
placeholder="输入新密码(至少6位)" />
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showResetPwd = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleResetPwd" :loading="resetPwdLoading">确认重置</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import api from '../../api'
|
||||||
|
import { useAuthStore } from '../../stores/auth'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const auth = useAuthStore()
|
||||||
|
const router = useRouter()
|
||||||
|
const activeTab = ref('password')
|
||||||
|
|
||||||
|
// === Change Password ===
|
||||||
|
const pwdForm = ref({ old_password: '', new_password: '', confirm_password: '' })
|
||||||
|
const pwdLoading = ref(false)
|
||||||
|
|
||||||
|
async function handleChangePassword() {
|
||||||
|
if (!pwdForm.value.old_password || !pwdForm.value.new_password) {
|
||||||
|
ElMessage.warning('请填写完整')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (pwdForm.value.new_password !== pwdForm.value.confirm_password) {
|
||||||
|
ElMessage.warning('两次密码不一致')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (pwdForm.value.new_password.length < 6) {
|
||||||
|
ElMessage.warning('密码至少6位')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pwdLoading.value = true
|
||||||
|
try {
|
||||||
|
const { data } = await api.post('/api/v1/auth/change-password/', {
|
||||||
|
old_password: pwdForm.value.old_password,
|
||||||
|
new_password: pwdForm.value.new_password,
|
||||||
|
})
|
||||||
|
ElMessage.success(data.message)
|
||||||
|
pwdForm.value = { old_password: '', new_password: '', confirm_password: '' }
|
||||||
|
// Force re-login
|
||||||
|
setTimeout(() => {
|
||||||
|
auth.logout()
|
||||||
|
router.push('/login')
|
||||||
|
}, 1500)
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error(e.response?.data?.message || '修改失败')
|
||||||
|
} finally {
|
||||||
|
pwdLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Admin Management ===
|
||||||
|
const admins = ref([])
|
||||||
|
const adminsLoading = ref(false)
|
||||||
|
const showCreateAdmin = ref(false)
|
||||||
|
const createAdminForm = ref({ username: '', password: '', is_superuser: false })
|
||||||
|
const createAdminLoading = ref(false)
|
||||||
|
const showResetPwd = ref(false)
|
||||||
|
const resetPwdUser = ref(null)
|
||||||
|
const resetPwdValue = ref('')
|
||||||
|
const resetPwdLoading = ref(false)
|
||||||
|
|
||||||
|
async function loadAdmins() {
|
||||||
|
if (!auth.user?.is_superuser) return
|
||||||
|
adminsLoading.value = true
|
||||||
|
try {
|
||||||
|
const { data } = await api.get('/api/v1/auth/admins/')
|
||||||
|
admins.value = data
|
||||||
|
} catch (e) {
|
||||||
|
admins.value = []
|
||||||
|
} finally {
|
||||||
|
adminsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCreateAdmin() {
|
||||||
|
if (!createAdminForm.value.username || !createAdminForm.value.password) {
|
||||||
|
ElMessage.warning('请填写完整')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
createAdminLoading.value = true
|
||||||
|
try {
|
||||||
|
const { data } = await api.post('/api/v1/auth/admins/create/', createAdminForm.value)
|
||||||
|
ElMessage.success(data.message)
|
||||||
|
showCreateAdmin.value = false
|
||||||
|
createAdminForm.value = { username: '', password: '', is_superuser: false }
|
||||||
|
await loadAdmins()
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error(e.response?.data?.message || '创建失败')
|
||||||
|
} finally {
|
||||||
|
createAdminLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleToggleAdmin(row) {
|
||||||
|
try {
|
||||||
|
const { data } = await api.post(`/api/v1/auth/admins/${row.id}/toggle/`)
|
||||||
|
ElMessage.success(data.message)
|
||||||
|
await loadAdmins()
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error(e.response?.data?.message || '操作失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openResetPwd(row) {
|
||||||
|
resetPwdUser.value = row
|
||||||
|
resetPwdValue.value = ''
|
||||||
|
showResetPwd.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleResetPwd() {
|
||||||
|
if (resetPwdValue.value.length < 6) {
|
||||||
|
ElMessage.warning('密码至少6位')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resetPwdLoading.value = true
|
||||||
|
try {
|
||||||
|
const { data } = await api.post(`/api/v1/auth/admins/${resetPwdUser.value.id}/reset-password/`, {
|
||||||
|
new_password: resetPwdValue.value,
|
||||||
|
})
|
||||||
|
ElMessage.success(data.message)
|
||||||
|
showResetPwd.value = false
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error(e.response?.data?.message || '重置失败')
|
||||||
|
} finally {
|
||||||
|
resetPwdLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Operation Logs ===
|
||||||
|
const logs = ref([])
|
||||||
|
const logsLoading = ref(false)
|
||||||
|
const logFilter = ref('')
|
||||||
|
const logLimit = ref(100)
|
||||||
|
|
||||||
|
async function loadLogs() {
|
||||||
|
logsLoading.value = true
|
||||||
|
try {
|
||||||
|
const params = { limit: logLimit.value }
|
||||||
|
if (logFilter.value) params.type = logFilter.value
|
||||||
|
const { data } = await api.get('/api/v1/alerts/', { params })
|
||||||
|
logs.value = data
|
||||||
|
} catch (e) {
|
||||||
|
logs.value = []
|
||||||
|
} finally {
|
||||||
|
logsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function logTagType(type) {
|
||||||
|
const map = { warning: 'warning', disable: 'danger', error: 'danger', manual: 'info' }
|
||||||
|
return map[type] || 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
function logTypeLabel(type) {
|
||||||
|
const map = { warning: '告警', disable: '自动停用', error: '错误', manual: '操作' }
|
||||||
|
return map[type] || type
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadAdmins()
|
||||||
|
loadLogs()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
Loading…
x
Reference in New Issue
Block a user