提交 dfe01cae authored 作者: 陈泽健's avatar 陈泽健

feat(service-monitor): P3级检测模块补全 + 巡检防重复触发 + 移动端响应式优化

检测项丰富化移植(阶段五):
- 新增 56_fastdfs_check.sh FastDFS功能验证检测(重构版,仅状态检测)
- 新增 57_android_check.sh Android设备检测
- 新增 59_log_export_check.sh 配置日志导出检测
- 新增 60_repair_capability_check.sh 修复能力检测
- check_modules.py 注册 4 个新模块,移除 58_data_backup_check
- display_names.py 追加 85 个 KEY 映射
- parser.py 追加 7 个状态词映射

巡检防重复触发优化(阶段六):
- runner_service.py 新增 get_running_for_target() 查询运行中巡检
- routes.py 新增 /api/service-monitor/run/status/<id> 端点
- routes.py SSE 流接口增加 409 防重检查
- run.html 前端检查运行状态,阻止重复触发
- index.html 巡检按钮增加预检查逻辑

移动端响应式优化(阶段七):
- 9 个页面全面优化移动端布局
- base.html: 顶部栏/侧边栏紧凑化,用户名溢出省略
- index.html: 目标卡片/按钮布局优化
- report.html: 报告头纵向排列,状态文字防竖向换行
- reports/schedule/targets: 筛选栏纵向,表格卡片化
- run.html: 巡检卡片紧凑化,取消按钮全宽
- statistics.html: 概览卡片缩小,趋势筛选纵向

验证:218 个单元测试全绿,5.44 full 套件实测通过
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 d50d0709
此差异已折叠。
#!/bin/bash
# 56_fastdfs_check.sh - FastDFS 功能验证检测
# 只检测服务状态,不执行实际上传下载操作(避免超时)
output_result() {
local key="$1"
local value="$2"
echo "${key}: ${value}"
}
# 从 config.sh 读取配置
CONTAINERS["tracker"]="tracker|utracker"
CONTAINERS["storage"]="storage|ustorage"
# 自动检测 FastDFS 容器
detect_containers() {
local tracker_container=""
local storage_container=""
# Tracker 容器
tracker_container=$(docker ps --format "{{.Names}}" | grep -iE "${CONTAINERS[tracker]}" | head -1)
if [[ -z "$tracker_container" ]]; then
tracker_container=$(docker ps -a --format "{{.Names}}" | grep -iE "${CONTAINERS[tracker]}" | head -1)
fi
# Storage 容器(可能与 Tracker 是同一个)
storage_container=$(docker ps --format "{{.Names}}" | grep -iE "${CONTAINERS[storage]}" | head -1)
if [[ -z "$storage_container" ]]; then
storage_container=$(docker ps -a --format "{{.Names}}" | grep -iE "${CONTAINERS[storage]}" | head -1)
fi
echo "$tracker_container|$storage_container"
}
# 主检测逻辑
main() {
local containers_raw
containers_raw=$(detect_containers)
local tracker_container="${containers_raw%%|*}"
local storage_container="${containers_raw##*|}"
# 1. 容器状态
if [[ -n "$tracker_container" ]]; then
local tracker_status
tracker_status=$(docker inspect -f '{{.State.Status}}' "$tracker_container" 2>/dev/null || echo "unknown")
output_result "FDFS_CONTAINER_TRACKER" "$tracker_container ($tracker_status)"
else
output_result "FDFS_CONTAINER_TRACKER" "未检测到"
fi
if [[ -n "$storage_container" ]] && [[ "$storage_container" != "$tracker_container" ]]; then
local storage_status
storage_status=$(docker inspect -f '{{.State.Status}}' "$storage_container" 2>/dev/null || echo "unknown")
output_result "FDFS_CONTAINER_STORAGE" "$storage_container ($storage_status)"
else
output_result "FDFS_CONTAINER_STORAGE" "与Tracker同容器"
fi
# 2. 端口检测(使用宿主机 ss,不依赖容器内命令)
local ports_ok=0
local ports_total=3
local ports_detail=""
# 22122 - Tracker
if ss -tln 2>/dev/null | grep -q ":22122 "; then
((ports_ok++))
ports_detail+="22122(Tracker):监听 "
else
ports_detail+="22122(Tracker):未监听 "
fi
# 23000 - Storage
if ss -tln 2>/dev/null | grep -q ":23000 "; then
((ports_ok++))
ports_detail+="23000(Storage):监听 "
else
ports_detail+="23000(Storage):未监听 "
fi
# 8888 - Nginx HTTP
if ss -tln 2>/dev/null | grep -q ":8888 "; then
((ports_ok++))
ports_detail+="8888(HTTP):监听"
else
ports_detail+="8888(HTTP):未监听"
fi
output_result "FDFS_PORTS_OK" "$ports_ok"
output_result "FDFS_PORTS_TOTAL" "$ports_total"
output_result "FDFS_PORTS_DETAIL" "$ports_detail"
if [[ $ports_ok -eq $ports_total ]]; then
output_result "FDFS_PORTS_LEVEL" "正常"
elif [[ $ports_ok -gt 0 ]]; then
output_result "FDFS_PORTS_LEVEL" "警告"
else
output_result "FDFS_PORTS_LEVEL" "严重"
fi
# 3. 进程检测(在 Tracker 容器内检查)
local target_container="${tracker_container:-$storage_container}"
if [[ -n "$target_container" ]]; then
local process_status=""
# 检查 fdfs_trackerd 进程
if docker exec "$target_container" ps aux 2>/dev/null | grep -q "[f]dfs_trackerd"; then
process_status+="trackerd:运行 "
else
process_status+="trackerd:未运行 "
fi
# 检查 fdfs_storaged 进程
if docker exec "$target_container" ps aux 2>/dev/null | grep -q "[f]dfs_storaged"; then
process_status+="storaged:运行"
else
process_status+="storaged:未运行"
fi
output_result "FDFS_PROCESS_STATUS" "$process_status"
if echo "$process_status" | grep -q "未运行"; then
output_result "FDFS_PROCESS_LEVEL" "警告"
else
output_result "FDFS_PROCESS_LEVEL" "正常"
fi
# 4. 配置文件检测
local client_conf="/etc/fdfs/client.conf"
if docker exec "$target_container" test -f "$client_conf" 2>/dev/null; then
output_result "FDFS_CONFIG_EXISTS" "yes"
local tracker_server
tracker_server=$(docker exec "$target_container" grep "^tracker_server" "$client_conf" 2>/dev/null | head -1 | awk -F= '{print $2}')
output_result "FDFS_TRACKER_SERVER" "${tracker_server:-未配置}"
output_result "FDFS_CONFIG_LEVEL" "正常"
else
output_result "FDFS_CONFIG_EXISTS" "no"
output_result "FDFS_TRACKER_SERVER" "配置文件不存在"
output_result "FDFS_CONFIG_LEVEL" "警告"
fi
else
output_result "FDFS_PROCESS_STATUS" "无容器"
output_result "FDFS_PROCESS_LEVEL" "严重"
output_result "FDFS_CONFIG_EXISTS" "no"
output_result "FDFS_TRACKER_SERVER" "无容器"
output_result "FDFS_CONFIG_LEVEL" "严重"
fi
# 5. HTTP 访问测试(简单探测,有超时)
if ss -tln 2>/dev/null | grep -q ":8888 "; then
local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 5 "http://127.0.0.1:8888/" 2>/dev/null || echo "000")
if [[ "$http_code" =~ ^(200|403|404)$ ]]; then
# 200=正常, 403/404=服务在运行只是路径问题
output_result "FDFS_HTTP_STATUS" "服务响应"
output_result "FDFS_HTTP_CODE" "$http_code"
output_result "FDFS_HTTP_LEVEL" "正常"
elif [[ "$http_code" == "000" ]]; then
output_result "FDFS_HTTP_STATUS" "连接失败"
output_result "FDFS_HTTP_CODE" "timeout"
output_result "FDFS_HTTP_LEVEL" "警告"
else
output_result "FDFS_HTTP_STATUS" "异常响应"
output_result "FDFS_HTTP_CODE" "$http_code"
output_result "FDFS_HTTP_LEVEL" "警告"
fi
else
output_result "FDFS_HTTP_STATUS" "端口未监听"
output_result "FDFS_HTTP_CODE" "N/A"
output_result "FDFS_HTTP_LEVEL" "严重"
fi
# 6. 总体状态
if [[ $ports_ok -eq $ports_total ]] && [[ -n "$target_container" ]]; then
output_result "FDFS_STATUS" "正常"
output_result "FDFS_STATUS_LEVEL" "正常"
elif [[ $ports_ok -gt 0 ]]; then
output_result "FDFS_STATUS" "部分异常"
output_result "FDFS_STATUS_LEVEL" "警告"
else
output_result "FDFS_STATUS" "服务异常"
output_result "FDFS_STATUS_LEVEL" "严重"
fi
}
main
#!/bin/bash
################################################################################
# 57_android_check.sh — Android 设备检测
# 功能: 检测 adb 可用性、设备连接状态
# 参考: AndroidCheck.psm1
# 日期: 2026-07-22
# 说明: 只检测不操作,检查 ADB 服务和设备连接状态
################################################################################
# 获取脚本所在目录并加载依赖
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="${LIB_DIR:-/tmp/check_modules}"
# 加载配置文件和通用函数库
if [ -f "$LIB_DIR/lib/config.sh" ]; then
source "$LIB_DIR/lib/config.sh"
else
echo "ERROR: 配置文件不存在: $LIB_DIR/lib/config.sh"
exit 1
fi
if [ -f "$LIB_DIR/lib/common.sh" ]; then
source "$LIB_DIR/lib/common.sh"
else
echo "ERROR: 通用函数库不存在: $LIB_DIR/lib/common.sh"
exit 1
fi
# ==================== ADB 工具检测 ====================
check_adb_available() {
local adb_path=""
local adb_ok="no"
# 1. 检查系统 PATH 中的 adb
if command -v adb >/dev/null 2>&1; then
adb_path=$(command -v adb)
adb_ok="yes"
fi
# 2. 检查常见安装位置
if [ "$adb_ok" = "no" ]; then
local adb_dirs=(
"/usr/local/bin/adb"
"/usr/bin/adb"
"/opt/android-sdk/platform-tools/adb"
"$HOME/Android/Sdk/platform-tools/adb"
"/data/tools/adb"
)
for dir in "${adb_dirs[@]}"; do
if [ -x "$dir" ]; then
adb_path="$dir"
adb_ok="yes"
break
fi
done
fi
output_result "ANDROID_ADB_EXISTS" "$adb_ok"
output_result "ANDROID_ADB_PATH" "${adb_path:-未找到}"
if [ "$adb_ok" = "yes" ]; then
# 获取 adb 版本
local adb_version
adb_version=$(adb version 2>/dev/null | head -1 || echo "未知")
output_result "ANDROID_ADB_VERSION" "$adb_version"
output_result "ANDROID_ADB_LEVEL" "正常"
else
output_result "ANDROID_ADB_VERSION" "未安装"
output_result "ANDROID_ADB_LEVEL" "警告"
fi
ANDROID_ADB_OK="$adb_ok"
}
# ==================== ADB 服务状态检测 ====================
check_adb_server() {
if [ "$ANDROID_ADB_OK" != "yes" ]; then
output_result "ANDROID_SERVER_STATUS" "ADB未安装"
output_result "ANDROID_SERVER_LEVEL" "警告"
return
fi
local server_running="no"
# 检查 adb server 是否运行
if pgrep -f "adb server" >/dev/null 2>&1 || pgrep -x "adb" >/dev/null 2>&1; then
server_running="yes"
fi
# 尝试启动 server(仅检测,不持续运行)
adb start-server >/dev/null 2>&1
if [ $? -eq 0 ]; then
server_running="yes"
fi
output_result "ANDROID_SERVER_STATUS" "$server_running"
if [ "$server_running" = "yes" ]; then
output_result "ANDROID_SERVER_LEVEL" "正常"
else
output_result "ANDROID_SERVER_LEVEL" "警告"
fi
}
# ==================== 设备连接检测 ====================
check_connected_devices() {
if [ "$ANDROID_ADB_OK" != "yes" ]; then
output_result "ANDROID_DEVICE_COUNT" "0"
output_result "ANDROID_DEVICE_LIST" "[]"
output_result "ANDROID_DEVICE_LEVEL" "警告"
return
fi
local device_count=0
local device_list=""
# 获取设备列表
local devices_output
devices_output=$(adb devices 2>/dev/null | grep -v "List of devices" | grep -v "^$" || echo "")
if [ -n "$devices_output" ]; then
while IFS= read -r line; do
local device_id device_status
device_id=$(echo "$line" | awk '{print $1}')
device_status=$(echo "$line" | awk '{print $2}')
if [ -n "$device_id" ] && [ "$device_id" != "daemon" ]; then
device_count=$((device_count + 1))
if [ -n "$device_list" ]; then
device_list="${device_list},"
fi
device_list="${device_list}{\"id\":\"${device_id}\",\"status\":\"${device_status}\"}"
fi
done <<< "$devices_output"
fi
output_result "ANDROID_DEVICE_COUNT" "$device_count"
output_result "ANDROID_DEVICE_LIST" "[${device_list}]"
if [ "$device_count" -gt 0 ]; then
output_result "ANDROID_DEVICE_LEVEL" "正常"
else
output_result "ANDROID_DEVICE_LEVEL" "警告"
fi
}
# ==================== 网络设备连接能力检测 ====================
check_network_adb_capability() {
if [ "$ANDROID_ADB_OK" != "yes" ]; then
output_result "ANDROID_NETWORK_CAPABLE" "no"
output_result "ANDROID_NETWORK_LEVEL" "警告"
return
fi
# 检查 adb 是否支持网络连接(tcpip)
local network_capable="yes"
# adb tcpip 命令存在性检查
if adb help 2>/dev/null | grep -q "tcpip"; then
network_capable="yes"
fi
output_result "ANDROID_NETWORK_CAPABLE" "$network_capable"
output_result "ANDROID_NETWORK_LEVEL" "正常"
}
# ==================== 主检测流程 ====================
main() {
log_info "开始 Android 设备检测..."
set +e
# 1. ADB 工具检测
check_adb_available
# 2. ADB 服务状态检测
check_adb_server
# 3. 已连接设备检测
check_connected_devices
# 4. 网络 ADB 能力检测
check_network_adb_capability
# 5. 综合状态
if [ "$ANDROID_ADB_OK" = "yes" ]; then
output_result "ANDROID_STATUS" "ADB已就绪"
output_result "ANDROID_STATUS_LEVEL" "正常"
else
output_result "ANDROID_STATUS" "ADB未安装"
output_result "ANDROID_STATUS_LEVEL" "警告"
fi
log_info "Android 设备检测完成"
}
main
\ No newline at end of file
#!/bin/bash
################################################################################
# 59_log_export_check.sh — 配置日志导出检测
# 功能: 检测关键配置文件和日志目录是否存在、大小、最近修改时间
# 参考: LogExport.psm1
# 日期: 2026-07-22
# 说明: 只检测不导出,验证配置和日志可获取性
################################################################################
# 获取脚本所在目录并加载依赖
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="${LIB_DIR:-/tmp/check_modules}"
# 加载配置文件和通用函数库
if [ -f "$LIB_DIR/lib/config.sh" ]; then
source "$LIB_DIR/lib/config.sh"
else
echo "ERROR: 配置文件不存在: $LIB_DIR/lib/config.sh"
exit 1
fi
if [ -f "$LIB_DIR/lib/common.sh" ]; then
source "$LIB_DIR/lib/common.sh"
else
echo "ERROR: 通用函数库不存在: $LIB_DIR/lib/common.sh"
exit 1
fi
# ==================== 平台类型识别 ====================
detect_platform_type() {
[ -d "/data/services" ] && echo "new" || echo "old"
}
# ==================== 配置文件检测 ====================
check_config_files() {
local platform_type
platform_type=$(detect_platform_type)
local total=0
local exists=0
local missing_list=""
local -a config_paths
if [ "$platform_type" = "new" ]; then
config_paths=(
"/data/services/web/pc/pc-vue2-ai/static/config.json"
"/data/services/web/pc/pc-vue2-backstage/static/config.json"
"/data/services/web/pc/pc-vue2-main/static/config.json"
"/data/services/web/pc/pc-vue2-meetingV2/static/config.json"
"/data/services/api/auth/auth-sso-auth/config"
"/data/services/api/auth/auth-sso-gateway/config"
"/data/services/api/auth/auth-sso-system/config"
"/data/services/api/java-meeting/java-meeting2.0/config"
"/data/services/api/java-meeting/java-meeting-extapi/config"
"/data/services/api/python-cmdb/setting.conf"
"/data/middleware/nginx/config"
"/data/middleware/redis/config"
"/data/middleware/emqx/config"
"/data/middleware/mysql/conf"
"/data/middleware/nacos/conf"
)
else
config_paths=(
"/var/www/java/ubains-web-2.0/static/config.json"
"/var/www/java/ubains-web-admin/static/config.json"
"/var/www/java/api-java-meeting2.0/config"
"/var/www/java/external-meeting-api/config"
"/var/www/html/setting.conf"
"/var/www/java/nginx-conf.d"
"/var/www/redis/redis.conf"
"/var/www/emqx/config"
)
fi
for path in "${config_paths[@]}"; do
total=$((total + 1))
if [ -e "$path" ]; then
exists=$((exists + 1))
else
if [ -n "$missing_list" ]; then
missing_list="${missing_list},"
fi
missing_list="${missing_list}${path}"
fi
done
output_result "LOGEXP_CONFIG_TOTAL" "$total"
output_result "LOGEXP_CONFIG_EXISTS" "$exists"
output_result "LOGEXP_CONFIG_MISSING" "$((total - exists))"
if [ "$exists" -eq "$total" ]; then
output_result "LOGEXP_CONFIG_LEVEL" "正常"
elif [ "$exists" -gt 0 ]; then
output_result "LOGEXP_CONFIG_LEVEL" "警告"
else
output_result "LOGEXP_CONFIG_LEVEL" "严重"
fi
}
# ==================== 日志目录检测 ====================
check_log_dirs() {
local platform_type
platform_type=$(detect_platform_type)
local total=0
local exists=0
local oversized=0
local log_info_list=""
local -a log_paths
if [ "$platform_type" = "new" ]; then
log_paths=(
"/data/services/api/auth/auth-sso-auth/log.out"
"/data/services/api/auth/auth-sso-gateway/log.out"
"/data/services/api/auth/auth-sso-system/log.out"
"/data/services/api/java-meeting/java-meeting2.0/logs"
"/data/services/api/java-meeting/java-meeting-extapi/logs"
"/data/services/api/java-meeting/java-message-scheduling/logs"
"/data/services/api/java-meeting/java-mqtt/logs"
"/data/services/api/java-meeting/java-quartz/logs"
"/data/services/api/python-cmdb/log"
"/data/middleware/nginx/log"
"/data/middleware/emqx/log"
"/data/middleware/mysql/logs"
"/data/middleware/nacos/logs"
)
else
log_paths=(
"/var/www/java/api-java-meeting2.0/logs"
"/var/www/java/external-meeting-api/logs"
"/var/www/html/log"
"/var/www/emqx/log"
"/usr/local/docker/mysql/logs"
)
fi
for path in "${log_paths[@]}"; do
total=$((total + 1))
if [ -e "$path" ]; then
exists=$((exists + 1))
# 获取大小
local size_human
size_human=$(du -sh "$path" 2>/dev/null | cut -f1 || echo "0")
# 检查是否过大(>1GB)
local size_bytes
size_bytes=$(du -sb "$path" 2>/dev/null | cut -f1 || echo "0")
if [ "$size_bytes" -gt 1073741824 ]; then
oversized=$((oversized + 1))
fi
fi
done
output_result "LOGEXP_LOG_TOTAL" "$total"
output_result "LOGEXP_LOG_EXISTS" "$exists"
output_result "LOGEXP_LOG_MISSING" "$((total - exists))"
output_result "LOGEXP_LOG_OVERSIZED" "$oversized"
if [ "$exists" -eq "$total" ] && [ "$oversized" -eq 0 ]; then
output_result "LOGEXP_LOG_LEVEL" "正常"
elif [ "$exists" -gt 0 ]; then
output_result "LOGEXP_LOG_LEVEL" "警告"
else
output_result "LOGEXP_LOG_LEVEL" "严重"
fi
}
# ==================== 关键日志大小检测 ====================
check_log_sizes() {
local platform_type
platform_type=$(detect_platform_type)
local -a check_files
if [ "$platform_type" = "new" ]; then
check_files=(
"/data/services/api/java-meeting/java-meeting2.0/logs/ubains-ERROR.log"
"/data/services/api/java-meeting/java-meeting-extapi/logs/ubains-ERROR.log"
"/data/services/api/python-cmdb/log/error.log"
)
else
check_files=(
"/var/www/java/api-java-meeting2.0/logs/ubains-ERROR.log"
"/var/www/java/external-meeting-api/logs/ubains-ERROR.log"
"/var/www/html/log/error.log"
)
fi
local total_size=0
local file_count=0
for file in "${check_files[@]}"; do
if [ -f "$file" ]; then
local fsize
fsize=$(stat -c '%s' "$file" 2>/dev/null || echo "0")
total_size=$((total_size + fsize))
file_count=$((file_count + 1))
fi
done
# 转换为 MB
local total_mb=$((total_size / 1048576))
output_result "LOGEXP_ERROR_LOG_COUNT" "$file_count"
output_result "LOGEXP_ERROR_LOG_SIZE_MB" "$total_mb"
if [ "$total_mb" -gt 500 ]; then
output_result "LOGEXP_ERROR_LOG_LEVEL" "警告"
else
output_result "LOGEXP_ERROR_LOG_LEVEL" "正常"
fi
}
# ==================== 日志轮转配置检测 ====================
check_logrotate() {
local rotate_ok="no"
local rotate_config=""
# 检查 logrotate 配置
if [ -d "/etc/logrotate.d" ]; then
local app_configs
app_configs=$(ls /etc/logrotate.d/ 2>/dev/null | grep -iE "ubains|meeting|nginx|redis|mysql|emqx|nacos" | head -5 || echo "")
if [ -n "$app_configs" ]; then
rotate_ok="yes"
rotate_config=$(echo "$app_configs" | tr '\n' ',' | sed 's/,$//')
fi
fi
output_result "LOGEXP_LOGROTATE_OK" "$rotate_ok"
output_result "LOGEXP_LOGROTATE_CONFIG" "${rotate_config:-}"
if [ "$rotate_ok" = "yes" ]; then
output_result "LOGEXP_LOGROTATE_LEVEL" "正常"
else
output_result "LOGEXP_LOGROTATE_LEVEL" "警告"
fi
}
# ==================== 导出能力检测 ====================
check_export_capability() {
local tar_ok="no"
local gzip_ok="no"
# 检查打包工具
if command -v tar >/dev/null 2>&1; then
tar_ok="yes"
fi
if command -v gzip >/dev/null 2>&1; then
gzip_ok="yes"
fi
output_result "LOGEXP_TAR_AVAILABLE" "$tar_ok"
output_result "LOGEXP_GZIP_AVAILABLE" "$gzip_ok"
if [ "$tar_ok" = "yes" ] && [ "$gzip_ok" = "yes" ]; then
output_result "LOGEXP_EXPORT_CAPABLE" "yes"
output_result "LOGEXP_EXPORT_LEVEL" "正常"
else
output_result "LOGEXP_EXPORT_CAPABLE" "no"
output_result "LOGEXP_EXPORT_LEVEL" "警告"
fi
}
# ==================== 主检测流程 ====================
main() {
log_info "开始配置日志导出检测..."
set +e
# 1. 配置文件检测
check_config_files
# 2. 日志目录检测
check_log_dirs
# 3. 关键日志大小检测
check_log_sizes
# 4. 日志轮转配置检测
check_logrotate
# 5. 导出能力检测
check_export_capability
# 6. 综合状态
output_result "LOGEXP_STATUS" "已检测"
output_result "LOGEXP_STATUS_LEVEL" "正常"
log_info "配置日志导出检测完成"
}
main
\ No newline at end of file
#!/bin/bash
################################################################################
# 60_repair_capability_check.sh — 修复能力检测
# 功能: 检测修复脚本/工具是否就绪,不执行修复
# 参考: issue_handler.sh
# 日期: 2026-07-22
# 说明: 只检测不修复,验证修复工具链可用性
################################################################################
# 获取脚本所在目录并加载依赖
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="${LIB_DIR:-/tmp/check_modules}"
# 加载配置文件和通用函数库
if [ -f "$LIB_DIR/lib/config.sh" ]; then
source "$LIB_DIR/lib/config.sh"
else
echo "ERROR: 配置文件不存在: $LIB_DIR/lib/config.sh"
exit 1
fi
if [ -f "$LIB_DIR/lib/common.sh" ]; then
source "$LIB_DIR/lib/common.sh"
else
echo "ERROR: 通用函数库不存在: $LIB_DIR/lib/common.sh"
exit 1
fi
# ==================== 平台类型识别 ====================
detect_platform_type() {
[ -d "/data/services" ] && echo "new" || echo "old"
}
# ==================== 修复脚本检测 ====================
check_repair_scripts() {
local scripts_ok="no"
local scripts_found=0
local scripts_list=""
# 常见修复脚本路径
local repair_script_paths=(
"/opt/troubleshoot/issue_handler.sh"
"/data/scripts/issue_handler.sh"
"/home/scripts/issue_handler.sh"
"/root/issue_handler.sh"
"/usr/local/bin/issue_handler.sh"
)
for script in "${repair_script_paths[@]}"; do
if [ -f "$script" ]; then
scripts_found=$((scripts_found + 1))
if [ -n "$scripts_list" ]; then
scripts_list="${scripts_list},"
fi
scripts_list="${scripts_list}${script}"
fi
done
if [ "$scripts_found" -gt 0 ]; then
scripts_ok="yes"
fi
output_result "REPAIR_SCRIPT_EXISTS" "$scripts_ok"
output_result "REPAIR_SCRIPT_COUNT" "$scripts_found"
output_result "REPAIR_SCRIPT_LIST" "${scripts_list:-}"
if [ "$scripts_ok" = "yes" ]; then
output_result "REPAIR_SCRIPT_LEVEL" "正常"
else
output_result "REPAIR_SCRIPT_LEVEL" "警告"
fi
}
# ==================== DNS 修复能力检测 ====================
check_dns_repair_capability() {
local capable="no"
local detail=""
# 检查 resolv.conf 是否可写
if [ -f "/etc/resolv.conf" ]; then
if [ -w "/etc/resolv.conf" ]; then
capable="yes"
detail="resolv.conf可写"
else
detail="resolv.conf只读"
fi
else
detail="resolv.conf不存在"
fi
# 检查 NetworkManager
if command -v nmcli >/dev/null 2>&1; then
detail="${detail},NetworkManager可用"
fi
output_result "REPAIR_DNS_CAPABLE" "$capable"
output_result "REPAIR_DNS_DETAIL" "${detail:-无工具}"
output_result "REPAIR_DNS_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== NTP 修复能力检测 ====================
check_ntp_repair_capability() {
local capable="no"
local detail=""
# 检查 chrony
if command -v chronyd >/dev/null 2>&1; then
capable="yes"
detail="chrony"
elif command -v ntpd >/dev/null 2>&1; then
capable="yes"
detail="ntp"
else
detail="未安装NTP服务"
fi
# 检查配置文件可写性
if [ -f "/etc/chrony.conf" ] && [ -w "/etc/chrony.conf" ]; then
detail="${detail},chrony.conf可写"
elif [ -f "/etc/ntp.conf" ] && [ -w "/etc/ntp.conf" ]; then
detail="${detail},ntp.conf可写"
fi
output_result "REPAIR_NTP_CAPABLE" "$capable"
output_result "REPAIR_NTP_DETAIL" "${detail:-无工具}"
output_result "REPAIR_NTP_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== 端口修复能力检测 ====================
check_port_repair_capability() {
local capable="no"
local detail=""
# 检查防火墙工具
if command -v firewall-cmd >/dev/null 2>&1; then
capable="yes"
detail="firewalld"
elif command -v iptables >/dev/null 2>&1; then
capable="yes"
detail="iptables"
else
detail="无防火墙工具"
fi
output_result "REPAIR_PORT_CAPABLE" "$capable"
output_result "REPAIR_PORT_DETAIL" "${detail:-无工具}"
output_result "REPAIR_PORT_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== 权限修复能力检测 ====================
check_permission_repair_capability() {
local capable="no"
local detail=""
# 检查 chmod/chown
if command -v chmod >/dev/null 2>&1 && command -v chown >/dev/null 2>&1; then
capable="yes"
detail="chmod+chown可用"
else
detail="权限工具缺失"
fi
# 检查关键目录是否可写
local platform_type
platform_type=$(detect_platform_type)
if [ "$platform_type" = "new" ]; then
if [ -w "/data/services/api" ] 2>/dev/null; then
detail="${detail},/data/services可写"
else
detail="${detail},/data/services不可写"
fi
else
if [ -w "/var/www/java" ] 2>/dev/null; then
detail="${detail},/var/www/java可写"
else
detail="${detail},/var/www/java不可写"
fi
fi
output_result "REPAIR_PERM_CAPABLE" "$capable"
output_result "REPAIR_PERM_DETAIL" "${detail:-无工具}"
output_result "REPAIR_PERM_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== Docker 修复能力检测 ====================
check_docker_repair_capability() {
local capable="no"
local detail=""
if command -v docker >/dev/null 2>&1; then
capable="yes"
# 检查 docker 权限
if docker ps >/dev/null 2>&1; then
detail="docker可用"
else
detail="docker无权限"
capable="no"
fi
else
detail="docker未安装"
fi
output_result "REPAIR_DOCKER_CAPABLE" "$capable"
output_result "REPAIR_DOCKER_DETAIL" "${detail:-无工具}"
output_result "REPAIR_DOCKER_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== 服务重启能力检测 ====================
check_service_restart_capability() {
local capable="no"
local detail=""
if command -v systemctl >/dev/null 2>&1; then
capable="yes"
detail="systemctl"
elif command -v service >/dev/null 2>&1; then
capable="yes"
detail="service"
else
detail="无服务管理工具"
fi
output_result "REPAIR_SERVICE_CAPABLE" "$capable"
output_result "REPAIR_SERVICE_DETAIL" "${detail:-无工具}"
output_result "REPAIR_SERVICE_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== 日志清理能力检测 ====================
check_log_cleanup_capability() {
local capable="no"
local detail=""
# 检查日志清理工具
local tools_found=0
if command -v logrotate >/dev/null 2>&1; then
tools_found=$((tools_found + 1))
detail="logrotate"
fi
if command -v lsof >/dev/null 2>&1; then
tools_found=$((tools_found + 1))
detail="${detail}+lsof"
fi
if command -v find >/dev/null 2>&1; then
tools_found=$((tools_found + 1))
detail="${detail}+find"
fi
if [ "$tools_found" -ge 2 ]; then
capable="yes"
fi
output_result "REPAIR_LOG_CLEAN_CAPABLE" "$capable"
output_result "REPAIR_LOG_CLEAN_DETAIL" "${detail:-工具不足}"
output_result "REPAIR_LOG_CLEAN_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== 主检测流程 ====================
main() {
log_info "开始修复能力检测..."
set +e
# 1. 修复脚本检测
check_repair_scripts
# 2. DNS 修复能力
check_dns_repair_capability
# 3. NTP 修复能力
check_ntp_repair_capability
# 4. 端口修复能力
check_port_repair_capability
# 5. 权限修复能力
check_permission_repair_capability
# 6. Docker 修复能力
check_docker_repair_capability
# 7. 服务重启能力
check_service_restart_capability
# 8. 日志清理能力
check_log_cleanup_capability
# 9. 综合状态
output_result "REPAIR_STATUS" "已检测"
output_result "REPAIR_STATUS_LEVEL" "正常"
log_info "修复能力检测完成"
}
main
\ No newline at end of file
......@@ -305,6 +305,14 @@ def api_run_stream():
target_id = request.args.get('target_id', '')
suite = request.args.get('suite', 'quick')
# 防重复触发:同一目标已有未完成的巡检时拒绝新请求
existing = runner_service.get_running_for_target(target_id)
if existing:
err = {"event": "error",
"message": f"该目标已有巡检正在运行 ({existing['done']}/{existing['total']}),请等待完成或取消后重试",
"run_id": existing["run_id"]}
return jsonify(err), 409
@stream_with_context
def generate():
try:
......@@ -321,6 +329,24 @@ def api_run_stream():
'Connection': 'keep-alive'})
@bp.route('/api/service-monitor/run/status/<target_id>', methods=['GET'])
def api_run_status(target_id):
"""查询指定目标是否有正在运行的巡检。
返回:
- {"running": true, "run_id", "done", "total", "current"} 或
- {"running": false}
"""
guard = _require_admin_json()
if guard:
return guard
run = runner_service.get_running_for_target(target_id)
if run:
return jsonify({"running": True, **run})
return jsonify({"running": False})
@bp.route('/api/service-monitor/run/<run_id>/cancel', methods=['POST'])
def api_cancel_run(run_id):
guard = _require_admin_json()
......
......@@ -87,6 +87,27 @@ def get_run_status(run_id: str) -> Optional[dict]:
}
def get_running_for_target(target_id: str) -> Optional[dict]:
"""获取指定目标正在运行的巡检任务(如有)。
用于防重复触发:同一目标已有未完成的巡检时返回该运行记录,
前端据此决定是否阻止启动新巡检。
"""
with _runs_lock:
for run_id, run in _runs.items():
if (run.get("target_id") == target_id
and not run.get("finished")):
return {
"run_id": run_id,
"target_id": run.get("target_id"),
"suite": run.get("suite"),
"done": run.get("done", 0),
"total": run.get("total", 0),
"current": run.get("current", ""),
}
return None
def run_inspection(target_id: str, suite: str):
"""巡检生成器:yield SSE 事件 dict。
......
......@@ -91,6 +91,10 @@ ALL_MODULES: List[CheckModule] = [
{SUITE_FULL}, "54_file_permission_check.sh"),
CheckModule("55_cron_expected", "定时任务预期检测", "system",
{SUITE_FULL}, "55_cron_expected.sh"),
CheckModule("59_log_export_check", "配置日志导出检测", "system",
{SUITE_FULL}, "59_log_export_check.sh"),
CheckModule("60_repair_capability_check", "修复能力检测", "system",
{SUITE_FULL}, "60_repair_capability_check.sh"),
# ---- service 类 ----
CheckModule("20_docker_basic", "Docker基础", "service",
{SUITE_QUICK, SUITE_FULL}, "20_docker_basic.sh"),
......@@ -130,6 +134,10 @@ ALL_MODULES: List[CheckModule] = [
{SUITE_FULL}, "39_middleware_conn.sh"),
CheckModule("51_service_check", "平台服务检测", "service",
{SUITE_FULL}, "51_service_check.sh"),
CheckModule("56_fastdfs_check", "FastDFS功能验证", "service",
{SUITE_FULL}, "56_fastdfs_check.sh"),
CheckModule("57_android_check", "Android设备检测", "service",
{SUITE_FULL}, "57_android_check.sh"),
]
_BY_ID: Dict[str, CheckModule] = {m.id: m for m in ALL_MODULES}
......
......@@ -771,4 +771,89 @@ DISPLAY_NAMES: Dict[str, str] = {
"CRON_EXPECTED_TOTAL": "预期任务数",
"CRON_EXPECTED_MISS_COUNT": "缺失任务数",
"CRON_EXPECTED_MISS_LIST": "缺失任务列表",
# ---- 56 FastDFS功能验证 ----
"FDFS_STATUS": "FastDFS状态",
"FDFS_STATUS_LEVEL": "FastDFS状态等级",
"FDFS_CONTAINER_TRACKER": "Tracker容器",
"FDFS_CONTAINER_STORAGE": "Storage容器",
"FDFS_PORTS_OK": "端口正常数",
"FDFS_PORTS_TOTAL": "端口总数",
"FDFS_PORTS_DETAIL": "端口详情",
"FDFS_PORTS_LEVEL": "端口检测等级",
"FDFS_PROCESS_STATUS": "进程状态",
"FDFS_PROCESS_LEVEL": "进程检测等级",
"FDFS_CONFIG_EXISTS": "配置文件存在",
"FDFS_CONFIG_LEVEL": "配置检测等级",
"FDFS_TRACKER_SERVER": "Tracker服务器",
"FDFS_HTTP_STATUS": "HTTP状态",
"FDFS_HTTP_LEVEL": "HTTP检测等级",
"FDFS_HTTP_CODE": "HTTP状态码",
# ---- 57 Android设备检测 ----
"ANDROID_STATUS": "Android检测状态",
"ANDROID_STATUS_LEVEL": "Android检测状态等级",
"ANDROID_ADB_EXISTS": "ADB工具存在",
"ANDROID_ADB_PATH": "ADB路径",
"ANDROID_ADB_VERSION": "ADB版本",
"ANDROID_ADB_LEVEL": "ADB检测等级",
"ANDROID_SERVER_STATUS": "ADB服务状态",
"ANDROID_SERVER_LEVEL": "ADB服务等级",
"ANDROID_DEVICE_COUNT": "已连接设备数",
"ANDROID_DEVICE_LIST": "设备列表",
"ANDROID_DEVICE_LEVEL": "设备检测等级",
"ANDROID_NETWORK_CAPABLE": "网络ADB能力",
"ANDROID_NETWORK_LEVEL": "网络ADB等级",
# ---- 59 配置日志导出检测 ----
"LOGEXP_STATUS": "日志导出检测状态",
"LOGEXP_STATUS_LEVEL": "日志导出检测状态等级",
"LOGEXP_CONFIG_TOTAL": "配置文件总数",
"LOGEXP_CONFIG_EXISTS": "配置文件存在数",
"LOGEXP_CONFIG_MISSING": "配置文件缺失数",
"LOGEXP_CONFIG_LEVEL": "配置文件等级",
"LOGEXP_LOG_TOTAL": "日志目录总数",
"LOGEXP_LOG_EXISTS": "日志目录存在数",
"LOGEXP_LOG_MISSING": "日志目录缺失数",
"LOGEXP_LOG_OVERSIZED": "日志过大数",
"LOGEXP_LOG_LEVEL": "日志目录等级",
"LOGEXP_ERROR_LOG_COUNT": "错误日志数",
"LOGEXP_ERROR_LOG_SIZE_MB": "错误日志大小MB",
"LOGEXP_ERROR_LOG_LEVEL": "错误日志等级",
"LOGEXP_LOGROTATE_OK": "日志轮转配置",
"LOGEXP_LOGROTATE_CONFIG": "轮转配置文件",
"LOGEXP_LOGROTATE_LEVEL": "日志轮转等级",
"LOGEXP_TAR_AVAILABLE": "tar可用",
"LOGEXP_GZIP_AVAILABLE": "gzip可用",
"LOGEXP_EXPORT_CAPABLE": "导出能力",
"LOGEXP_EXPORT_LEVEL": "导出能力等级",
# ---- 60 修复能力检测 ----
"REPAIR_STATUS": "修复能力检测状态",
"REPAIR_STATUS_LEVEL": "修复能力检测状态等级",
"REPAIR_SCRIPT_EXISTS": "修复脚本存在",
"REPAIR_SCRIPT_COUNT": "修复脚本数",
"REPAIR_SCRIPT_LIST": "修复脚本列表",
"REPAIR_SCRIPT_LEVEL": "修复脚本等级",
"REPAIR_DNS_CAPABLE": "DNS修复能力",
"REPAIR_DNS_DETAIL": "DNS修复详情",
"REPAIR_DNS_LEVEL": "DNS修复等级",
"REPAIR_NTP_CAPABLE": "NTP修复能力",
"REPAIR_NTP_DETAIL": "NTP修复详情",
"REPAIR_NTP_LEVEL": "NTP修复等级",
"REPAIR_PORT_CAPABLE": "端口修复能力",
"REPAIR_PORT_DETAIL": "端口修复详情",
"REPAIR_PORT_LEVEL": "端口修复等级",
"REPAIR_PERM_CAPABLE": "权限修复能力",
"REPAIR_PERM_DETAIL": "权限修复详情",
"REPAIR_PERM_LEVEL": "权限修复等级",
"REPAIR_DOCKER_CAPABLE": "Docker修复能力",
"REPAIR_DOCKER_DETAIL": "Docker修复详情",
"REPAIR_DOCKER_LEVEL": "Docker修复等级",
"REPAIR_SERVICE_CAPABLE": "服务重启能力",
"REPAIR_SERVICE_DETAIL": "服务重启详情",
"REPAIR_SERVICE_LEVEL": "服务重启等级",
"REPAIR_LOG_CLEAN_CAPABLE": "日志清理能力",
"REPAIR_LOG_CLEAN_DETAIL": "日志清理详情",
"REPAIR_LOG_CLEAN_LEVEL": "日志清理等级",
}
......@@ -42,6 +42,13 @@ _VALUE_STATUS_MAP = {
"查询失败": WARNING, "无法读取": WARNING,
# 平台服务检测 / 端口检测状态词
"监听": NORMAL, "未监听": CRITICAL,
# FastDFS 功能验证检测状态词
"yes": NORMAL, "no": WARNING,
"上传失败": CRITICAL, "下载失败": CRITICAL,
"命令不存在": CRITICAL, "配置文件缺失": CRITICAL,
# Android / 备份 / 日志导出 / 修复能力检测状态词
"ADB未安装": WARNING, "未安装": WARNING,
"无工具": WARNING, "工具不足": WARNING,
}
# 无效值/未安装占位符(这些值表示服务未安装或检测失败,不作为正常检测项)
......
......@@ -191,10 +191,11 @@
}
.topbar {
left: 0;
padding: 0 12px;
}
.main-content {
margin-left: 0;
padding: 84px 14px 28px;
padding: 72px 12px 24px;
}
.hamburger {
display: block;
......@@ -202,6 +203,29 @@
.topbar-home {
display: none;
}
.topbar-user {
font-size: 13px;
gap: 8px;
}
.topbar-user span {
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.btn-logout {
padding: 6px 10px;
font-size: 12px;
}
/* 侧边栏菜单项 */
.nav-item {
padding: 12px 16px;
font-size: 13px;
}
.sidebar-brand {
font-size: 16px;
padding: 16px;
}
}
</style>
{% block extra_css %}{% endblock %}
......
......@@ -22,8 +22,8 @@
</div>
{% if is_admin %}
<div class="target-actions">
<a href="/service-monitor/run/{{ t.id }}?suite=quick" class="btn-suite btn-quick">⚡ 快速巡检</a>
<a href="/service-monitor/run/{{ t.id }}?suite=full" class="btn-suite btn-full">🔍 全量巡检</a>
<a href="/service-monitor/run/{{ t.id }}?suite=quick" class="btn-suite btn-quick" onclick="return checkBeforeRun('/service-monitor/run/{{ t.id }}?suite=quick', '{{ t.id }}')">⚡ 快速巡检</a>
<a href="/service-monitor/run/{{ t.id }}?suite=full" class="btn-suite btn-full" onclick="return checkBeforeRun('/service-monitor/run/{{ t.id }}?suite=full', '{{ t.id }}')">🔍 全量巡检</a>
</div>
{% else %}
<div class="target-actions">
......@@ -66,6 +66,37 @@
}
@media screen and (max-width: 1024px) { .target-grid { grid-template-columns: repeat(2, 1fr); } }
@media screen and (max-width: 768px) { .target-grid { grid-template-columns: 1fr; } }
@media screen and (max-width: 768px) {
.target-grid { grid-template-columns: 1fr; gap: 12px; }
.target-card { padding: 16px; }
.target-card-head { margin-bottom: 10px; }
.target-icon { font-size: 24px; }
.target-name { font-size: 15px; }
.target-meta { font-size: 12px; margin-bottom: 12px; }
.target-actions { flex-direction: column; gap: 8px; }
.btn-suite { padding: 10px 0; font-size: 13px; }
.section-head { margin-bottom: 12px; }
.section-title { font-size: 16px; }
.btn-add { padding: 8px 14px; font-size: 13px; }
}
</style>
{% endblock %}
{% block extra_js %}
<script>
async function checkBeforeRun(href, targetId) {
try {
const resp = await fetch(`/api/service-monitor/run/status/${encodeURIComponent(targetId)}`, {credentials:'include'});
if (resp.ok) {
const status = await resp.json();
if (status.running) {
alert(`该目标已有巡检正在运行(${status.done}/${status.total}),请等待完成或取消后再试`);
return false;
}
}
} catch(err) {}
window.location.href = href;
return false;
}
</script>
{% endblock %}
\ No newline at end of file
......@@ -36,8 +36,21 @@
.actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--gray-200); }
@media screen and (max-width: 768px) {
.config-section { margin-bottom: 14px; border-radius: 10px; }
.section-header { padding: 12px 14px; }
.section-title { font-size: 14px; }
.section-body { padding: 14px; }
.form-row { flex-direction: column; gap: 0; }
.form-row .form-group { margin-bottom: 14px; }
.form-group input, .form-group select, .form-group textarea {
padding: 8px 10px; font-size: 13px;
}
.toggle-row { padding: 10px 0; }
.toggle-label { font-size: 13px; }
.btn-test { padding: 6px 12px; font-size: 12px; }
.btn-save { padding: 8px 16px; font-size: 13px; width: 100%; }
.actions { flex-direction: column; }
.section-head { margin-bottom: 12px; }
}
</style>
{% endblock %}
......
......@@ -107,12 +107,28 @@
.long-value.expanded .full { display: inline; white-space: pre-wrap; word-break: break-all; }
@media screen and (max-width: 768px) {
.summary-cards { grid-template-columns: repeat(2,1fr); }
.abnormal-row { grid-template-columns: 24px 1fr auto; gap: 6px; }
.abnormal-row .ab-threshold, .abnormal-row .ab-module { display: none; }
.item-row { grid-template-columns: 1fr 1fr; gap: 6px; }
.container { padding: 0; }
.report-head { flex-direction: column; align-items: flex-start; gap: 10px; }
.report-meta h2 { font-size: 16px; }
.export-wrap { width: 100%; }
.btn-export { width: 100%; }
.export-menu { position: fixed; left: 12px; right: 12px; top: auto; bottom: 12px; border-radius: 12px; }
.summary-cards { grid-template-columns: repeat(2, 1fr); gap: 8px; }
.sum-card { padding: 12px; }
.sum-card .num { font-size: 22px; }
.abnormal-row { grid-template-columns: 20px 1fr auto; gap: 6px; padding: 10px 12px; font-size: 13px; }
.abnormal-row .ab-threshold, .abnormal-row .ab-module, .abnormal-row .btn-fix { display: none; }
.ab-name { font-size: 13px; }
.module-header { padding: 12px 14px; }
.module-title { font-size: 14px; }
.module-mini { font-size: 12px; }
.item-row { grid-template-columns: 1fr auto; gap: 4px; padding: 8px 14px; font-size: 13px; }
.item-threshold { display: none; }
.item-status { white-space: nowrap; }
.process-table { font-size: 11px; }
.process-table .cmd-cell { max-width: 150px; }
.process-table th, .process-table td { padding: 4px 6px; }
.process-table .cmd-cell { max-width: 120px; }
.guest-banner { flex-direction: column; text-align: center; gap: 8px; padding: 12px; }
}
</style>
{% endblock %}
......
......@@ -104,9 +104,20 @@
.btn-batch-del:hover { background: #fecaca; }
@media screen and (max-width: 768px) {
.report-row { grid-template-columns: 32px 1fr 1fr; gap: 6px; }
.report-summary, .report-time { display: none; }
.filter-bar { flex-wrap: wrap; }
.filter-bar { flex-direction: column; align-items: stretch; gap: 8px; }
.filter-bar select { width: 100%; padding: 8px 10px; font-size: 13px; }
.report-count { margin-left: 0; text-align: right; font-size: 12px; }
.report-row {
grid-template-columns: 28px 1fr auto;
gap: 8px; padding: 12px 14px;
border-bottom: 2px solid var(--gray-200);
}
.report-summary, .report-time, .btn-view { display: none; }
.report-target { font-size: 14px; }
.report-suite { font-size: 12px; }
.batch-bar { flex-wrap: wrap; gap: 10px; padding: 10px 14px; }
.btn-batch-del { margin-left: 0; }
.section-head { margin-bottom: 12px; }
}
</style>
{% endblock %}
......
......@@ -37,6 +37,20 @@
.done-ok { background: #dcfce7; color: #15803d; }
.done-err { background: #fee2e2; color: var(--red); }
.btn-report { display: inline-block; margin-top: 12px; background: var(--primary); color: #fff; padding: 9px 22px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; }
@media screen and (max-width: 768px) {
.run-card { padding: 16px; border-radius: 12px; }
.run-head { flex-direction: column; align-items: flex-start; gap: 10px; }
.run-title { font-size: 15px; }
.run-title .suite-tag { font-size: 12px; padding: 2px 8px; }
.btn-cancel { width: 100%; padding: 8px; }
.progress-text { flex-direction: column; gap: 2px; font-size: 12px; }
.module-item { gap: 8px; padding: 10px 0; }
.module-status { font-size: 16px; width: 20px; }
.module-name { font-size: 13px; }
.module-info { font-size: 11px; }
.btn-report { padding: 8px 18px; font-size: 13px; }
}
</style>
{% endblock %}
......@@ -164,6 +178,36 @@
}
}
/* 显示“已有巡检正在运行”的占位状态(不触发新巡检) */
function showAlreadyRunning(runId, done, total) {
currentRunId = runId;
if (total) {
const pct = Math.round((done / total) * 100);
document.getElementById('progress-fill').style.width = pct + '%';
document.getElementById('progress-count').textContent = `${done} / ${total}`;
}
document.getElementById('progress-current').textContent = '该目标已有巡检正在运行,请等待完成或取消后重试';
const banner = document.getElementById('done-banner');
const text = document.getElementById('done-text');
banner.classList.add('show');
banner.className = 'done-banner show done-err';
text.textContent = `⚠️ 该目标已有巡检正在运行${total ? `(${done}/${total})` : ''},可点击右上角“取消”终止已有巡检后再重试`;
}
async function checkAndStart() {
try {
const resp = await fetch(`/api/service-monitor/run/status/${encodeURIComponent(TARGET_ID)}`, {credentials:'include'});
if (resp.ok) {
const status = await resp.json();
if (status.running) {
showAlreadyRunning(status.run_id, status.done || 0, status.total || 0);
return;
}
}
} catch(e) { /* 查询失败则按原流程启动 */ }
startInspection();
}
async function cancelRun() {
if (currentRunId) {
try { await fetch(`/api/service-monitor/run/${currentRunId}/cancel`, {method:'POST', credentials:'include'}); } catch(e) {}
......@@ -172,6 +216,6 @@
finishRun(false, null, null, '巡检已取消');
}
window.addEventListener('DOMContentLoaded', startInspection);
window.addEventListener('DOMContentLoaded', checkAndStart);
</script>
{% endblock %}
\ No newline at end of file
......@@ -86,11 +86,25 @@
}
@media screen and (max-width: 768px) {
.table-head { display: none; }
.table-row { grid-template-columns: 1fr 1fr; gap: 6px; padding: 14px; }
.modal { padding: 20px; max-width: 100%; }
.repeat-group { flex-direction: column; }
.table-row {
grid-template-columns: 1fr 1fr;
gap: 6px; padding: 14px;
border-bottom: 2px solid var(--gray-200);
}
.table-row .name { font-size: 15px; }
.schedule-desc { font-size: 12px; }
.actions { flex-wrap: wrap; gap: 4px; }
.btn-sm { padding: 4px 8px; font-size: 11px; }
.modal { padding: 16px; max-width: 100%; border-radius: 12px; }
.modal h3 { font-size: 16px; }
.repeat-group { flex-direction: column; gap: 6px; }
.repeat-btn { padding: 8px 0; font-size: 13px; }
.date-row { flex-direction: column; gap: 0; }
.date-row .form-group { margin-bottom: 14px; }
.time-picker select { width: 70px; padding: 8px 6px; font-size: 13px; }
.weekday-btn { width: 38px; height: 32px; font-size: 12px; }
.section-head { flex-direction: column; align-items: flex-start; gap: 10px; }
.btn-add { width: 100%; text-align: center; }
}
</style>
{% endblock %}
......
......@@ -60,10 +60,21 @@
}
@media screen and (max-width: 768px) {
.overview-cards { grid-template-columns: repeat(2,1fr); }
.filter-bar { gap: 10px; padding: 12px; }
.filter-group { flex: 1; min-width: 140px; }
.filter-label { font-size: 13px; }
.filter-select, .filter-date { padding: 6px 10px; font-size: 13px; }
.btn-query { padding: 6px 14px; font-size: 13px; }
.overview-cards { grid-template-columns: repeat(2, 1fr); gap: 8px; }
.ov-card { padding: 12px; }
.ov-card .value { font-size: 22px; }
.ov-card .label { font-size: 12px; }
.trend-layout { flex-direction: column; }
.filter-bar { gap: 10px; }
.chart-container { min-height: 280px; }
.chart-section { padding: 14px; margin-bottom: 14px; }
.chart-title { font-size: 14px; margin-bottom: 12px; }
.chart-container { min-height: 250px; }
.item-selector { max-width: 100%; font-size: 13px; }
.trend-filters { flex-direction: column; gap: 10px; }
}
</style>
{% endblock %}
......
......@@ -38,7 +38,18 @@
@media screen and (max-width: 768px) {
.table-head { display: none; }
.table-row { grid-template-columns: 1fr 1fr; gap: 6px; padding: 14px; }
.table-row {
grid-template-columns: 1fr 1fr;
gap: 6px; padding: 14px;
border-bottom: 2px solid var(--gray-200);
}
.table-row .name { font-size: 15px; }
.actions { flex-wrap: wrap; gap: 4px; }
.btn-sm { padding: 4px 8px; font-size: 11px; }
.modal { padding: 16px; max-width: 100%; border-radius: 12px; }
.modal h3 { font-size: 16px; }
.section-head { flex-direction: column; align-items: flex-start; gap: 10px; }
.btn-add { width: 100%; text-align: center; }
}
</style>
{% endblock %}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论