【调整】监控添加调整

This commit is contained in:
chenzhihua
2025-07-17 17:20:19 +08:00
parent 3e5a41a7e7
commit 11f2b7c2f6
54 changed files with 302 additions and 198 deletions

View File

@@ -61,7 +61,7 @@ export interface WorkflowHistoryItem {
export interface OverviewData {
workflow: WorkflowOverview
cert: CertOverview
site_monitor: SiteMonitorOverview
monitor: SiteMonitorOverview
workflow_history: WorkflowHistoryItem[]
}

View File

@@ -131,7 +131,7 @@ export default defineComponent({
<div class="flex items-center xl:space-x-5 lg:space-x-4 md:space-x-3 space-x-3">
<div>
<span class="xl:text-[2.4rem] lg:text-[2.2rem] md:text-[2rem] text-[1.8rem] font-bold">
{overviewData.value.site_monitor.count}
{overviewData.value.monitor.count}
</span>
<p class={styles.tableText}>{$t('t_3_1746773348798')}</p>
</div>
@@ -139,7 +139,7 @@ export default defineComponent({
<div class="flex items-center space-x-1">
<span class={`w-4 h-4 rounded-full mr-[.6rem] ${styles.bgUtilError}`}></span>
<span class={styles.tableText}>
{$t('t_7_1746773349302')}: {overviewData.value.site_monitor.exception}
{$t('t_7_1746773349302')}: {overviewData.value.monitor.exception}
</span>
</div>
</div>

View File

@@ -51,12 +51,12 @@ export const useHomeStore = defineStore('home-store', (): HomeStoreExposes => {
const overviewData = ref<OverviewData>({
workflow: { count: 0, active: 0, failure: 0 },
cert: { count: 0, will: 0, end: 0 },
site_monitor: { count: 0, exception: 0 },
monitor: { count: 0, exception: 0 },
workflow_history: [],
});
})
// 错误处理
const { handleError } = useError();
const { handleError } = useError()
// -------------------- 请求方法 --------------------
/**
@@ -70,7 +70,7 @@ export const useHomeStore = defineStore('home-store', (): HomeStoreExposes => {
loading.value = true
const { data, status } = await getOverviews().fetch()
if (status) {
const { workflow, cert, site_monitor, workflow_history } = data
const { workflow, cert, monitor, workflow_history } = data
overviewData.value = {
workflow: {
count: workflow?.count || 0,
@@ -78,7 +78,7 @@ export const useHomeStore = defineStore('home-store', (): HomeStoreExposes => {
failure: workflow?.failure || 0,
},
cert: { count: cert?.count || 0, will: cert?.will || 0, end: cert?.end || 0 },
site_monitor: { count: site_monitor?.count || 0, exception: site_monitor?.exception || 0 },
monitor: { count: monitor?.count || 0, exception: monitor?.exception || 0 },
workflow_history: workflow_history || [],
}
}

View File

@@ -34,8 +34,6 @@ const ErrorListCard = defineComponent({
return new Date(dateStr).toLocaleString('zh-CN')
}
console.log('ErrorListCard 渲染monitorId:', props.monitorId)
// 错误记录表格配置
const errorColumns = [
{
@@ -43,14 +41,14 @@ const ErrorListCard = defineComponent({
key: 'create_time',
width: 200,
render: (row: ErrorRecord) => (
<span class="text-[1.4rem] sm:text-[1.5rem] font-mono">{formatDateTime(row.create_time)}</span>
<span class="text-[1.3rem] sm:text-[1.4rem] font-mono">{formatDateTime(row.create_time)}</span>
),
},
{
title: '错误消息',
key: 'msg',
render: (row: ErrorRecord) => (
<div class="text-[1.4rem] sm:text-[1.5rem] text-red-600 dark:text-red-400 break-words leading-relaxed">
<div class="text-[1.3rem] sm:text-[1.4rem] text-red-600 dark:text-red-400 break-words leading-relaxed">
<div class="max-w-full overflow-hidden">
<div class="whitespace-pre-wrap break-all">{row.msg || '-'}</div>
</div>
@@ -61,30 +59,17 @@ const ErrorListCard = defineComponent({
// 错误记录请求函数
const errorRecordRequest = async <T = ErrorRecord,>(params: GetErrorRecordParams) => {
console.log('🔍 错误记录请求开始,参数:', params)
console.log('🔍 API端点: /v1/monitor/get_err_record')
try {
const apiInstance = getMonitorErrorRecord(params)
console.log('🔍 API实例创建成功:', apiInstance)
const response = await apiInstance.fetch()
console.log('✅ 错误记录API响应成功:', response)
const { data, count, status, message } = response
console.log('📊 响应详情:', { data, count, status, message })
const { data, count } = response
const result = {
list: (data || []) as T[],
total: count || 0,
}
console.log('🎯 错误记录处理结果:', result)
return result
} catch (error: unknown) {
console.error('❌ 错误记录请求失败:', error)
if (error instanceof Error) {
console.error('❌ 错误消息:', error.message)
console.error('❌ 错误堆栈:', error.stack)
}
handleError(error).default('获取错误记录失败,请稍后重试')
return { list: [] as T[], total: 0 }
}
@@ -110,23 +95,18 @@ const ErrorListCard = defineComponent({
// 组件挂载时加载错误记录
onMounted(async () => {
console.log('🚀 ErrorListCard 挂载,开始加载错误记录')
console.log('🚀 监控ID:', props.monitorId)
// 使用useTable的fetch方法加载数据
try {
console.log('📊 使用useTable fetch方法...')
await fetchErrorList()
console.log('✅ 错误记录加载完成')
} catch (error) {
console.error('❌ 错误记录加载失败:', error)
} catch {
// 错误处理已在errorRecordRequest中处理
}
})
return () => (
<NCard
title="错误列表"
class="h-fit [&_.n-card-header_.n-card-header__main]:text-[1.8rem] [&_.n-card-header_.n-card-header__main]:font-medium"
class="h-fit [&_.n-card-header_.n-card-header__main]:text-[1.5rem] [&_.n-card-header_.n-card-header__main]:font-medium"
bordered
>
{{
@@ -136,7 +116,7 @@ const ErrorListCard = defineComponent({
</NIcon>
),
default: () => (
<div class="space-y-4">
<div class="space-y-3">
<NSpin show={errorLoading.value}>
<TableComponent>
{{
@@ -144,11 +124,11 @@ const ErrorListCard = defineComponent({
<NEmpty
description="暂无错误记录"
size="large"
class="[&_.n-empty__description]:text-[1.6rem] py-8"
class="[&_.n-empty__description]:text-[1.4rem] py-6"
>
{{
icon: () => (
<NIcon size="48" color="var(--n-text-color-disabled)">
<NIcon size="40" color="var(--n-text-color-disabled)">
<ErrorOutline />
</NIcon>
),
@@ -158,7 +138,7 @@ const ErrorListCard = defineComponent({
}}
</TableComponent>
</NSpin>
<div class="flex justify-end mt-4">
<div class="flex justify-end mt-3">
<PageComponent />
</div>
</div>
@@ -269,8 +249,8 @@ export default defineComponent({
/**
* 递归渲染证书链节点
*/
const renderCertChainNode = (node: CertChainNode, level: number = 0, index: number = 0): JSX.Element[] => {
const elements: JSX.Element[] = []
const renderCertChainNode = (node: CertChainNode, level: number = 0, index: number = 0) => {
const elements = []
// 确定证书类型和样式
const getCertTypeInfo = (level: number, hasChildren: boolean) => {
@@ -295,19 +275,19 @@ export default defineComponent({
}
}
const typeInfo = getCertTypeInfo(level, node.children && node.children.length > 0)
const typeInfo = getCertTypeInfo(level, Boolean(node.children && node.children.length > 0))
// 渲染当前节点
elements.push(
<div
key={`cert-${level}-${index}`}
class="flex items-center space-x-3 p-3 bg-white dark:bg-gray-800 rounded-lg shadow-sm"
style={{ marginLeft: `${level * 1.5}rem` }}
class="flex items-center space-x-3 p-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm"
style={{ marginLeft: `${level * 1.2}rem` }}
>
<div class={`w-3 h-3 ${typeInfo.color} rounded-full flex-shrink-0 shadow-sm`}></div>
<div class={`w-2.5 h-2.5 ${typeInfo.color} rounded-full flex-shrink-0 shadow-sm`}></div>
<div class="flex-1">
<span class={`text-[1.4rem] sm:text-[1.5rem] font-medium ${typeInfo.textColor}`}>{typeInfo.label}</span>
<div class="text-[1.3rem] sm:text-[1.4rem] text-gray-600 dark:text-gray-400 font-mono mt-1 break-words">
<span class={`text-[1.2rem] sm:text-[1.3rem] font-medium ${typeInfo.textColor}`}>{typeInfo.label}</span>
<div class="text-[1.1rem] sm:text-[1.2rem] text-gray-600 dark:text-gray-400 font-mono mt-1 break-words">
{node.common_name}
</div>
</div>
@@ -330,16 +310,16 @@ export default defineComponent({
})
return () => (
<div class="mx-auto max-w-[1800px] w-full p-4 sm:p-6 lg:p-8" style={cssVars.value}>
<div class="mx-auto max-w-[1800px] w-full p-3 sm:p-4 lg:p-6" style={cssVars.value}>
<NSpin show={loading.value}>
{/* 页面头部 */}
<div class="mb-6 sm:mb-8">
<NSpace align="center" class="mb-4 sm:mb-5">
<div class="mb-4 sm:mb-5">
<NSpace align="center" class="mb-3 sm:mb-4">
<NButton
size="medium"
type="default"
onClick={goBack}
class="text-[1.4rem] sm:text-[1.5rem]"
class="text-[1.3rem] sm:text-[1.4rem]"
renderIcon={() => (
<NIcon>
<ArrowLeft />
@@ -349,18 +329,18 @@ export default defineComponent({
</NButton>
</NSpace>
<h1 class="text-[2.2rem] sm:text-[2.4rem] lg:text-[2.6rem] font-semibold text-gray-800 dark:text-gray-200 break-words leading-tight">
<h1 class="text-[1.8rem] sm:text-[2rem] lg:text-[2.2rem] font-semibold text-gray-800 dark:text-gray-200 break-words leading-tight">
{detailData.value?.name || '监控详情'} -
</h1>
</div>
{/* 内容区域 */}
{detailData.value ? (
<div class="space-y-6 sm:space-y-8 lg:space-y-10">
<div class="space-y-4 sm:space-y-5 lg:space-y-6">
{/* 合并的监控和证书详情模块 */}
<NCard
title="监控详情与证书信息"
class="[&_.n-card-header_.n-card-header__main]:text-[1.8rem] [&_.n-card-header_.n-card-header__main]:font-medium"
class="[&_.n-card-header_.n-card-header__main]:text-[1.5rem] [&_.n-card-header_.n-card-header__main]:font-medium"
bordered
>
{{
@@ -370,21 +350,21 @@ export default defineComponent({
</NIcon>
),
default: () => (
<div class="space-y-10">
<div class="space-y-6">
{/* 核心状态信息 - 最重要 */}
<div>
<h4 class="font-semibold mb-6 text-primary text-[1.9rem] sm:text-[2rem] border-b-2 border-primary/20 pb-3">
<h4 class="font-semibold mb-4 text-primary text-[1.6rem] sm:text-[1.7rem] border-b-2 border-primary/20 pb-2">
</h4>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="bg-gradient-to-br from-blue-50 to-blue-100 dark:from-blue-900/30 dark:to-blue-800/30 p-5 rounded-xl border border-blue-200 dark:border-blue-700">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="bg-gradient-to-br from-blue-50 to-blue-100 dark:from-blue-900/30 dark:to-blue-800/30 p-4 rounded-xl border border-blue-200 dark:border-blue-700">
<NText
depth="3"
class="text-[1.5rem] sm:text-[1.6rem] font-medium text-blue-700 dark:text-blue-300"
class="text-[1.3rem] sm:text-[1.4rem] font-medium text-blue-700 dark:text-blue-300"
>
</NText>
<div class="mt-3 font-bold text-[1.7rem] sm:text-[1.8rem]">
<div class="mt-2 font-bold text-[1.5rem] sm:text-[1.6rem]">
{detailData.value && (
<span style={{ color: getValidStatus(detailData.value.valid).color }}>
{getValidStatus(detailData.value.valid).text}
@@ -392,14 +372,14 @@ export default defineComponent({
)}
</div>
</div>
<div class="bg-gradient-to-br from-green-50 to-green-100 dark:from-green-900/30 dark:to-green-800/30 p-5 rounded-xl border border-green-200 dark:border-green-700">
<div class="bg-gradient-to-br from-green-50 to-green-100 dark:from-green-900/30 dark:to-green-800/30 p-4 rounded-xl border border-green-200 dark:border-green-700">
<NText
depth="3"
class="text-[1.5rem] sm:text-[1.6rem] font-medium text-green-700 dark:text-green-300"
class="text-[1.3rem] sm:text-[1.4rem] font-medium text-green-700 dark:text-green-300"
>
</NText>
<div class="mt-3 font-bold text-[1.7rem] sm:text-[1.8rem]">
<div class="mt-2 font-bold text-[1.5rem] sm:text-[1.6rem]">
{detailData.value && (
<span style={{ color: getDaysLeftColor(detailData.value.days_left) }}>
{detailData.value.days_left}
@@ -407,14 +387,14 @@ export default defineComponent({
)}
</div>
</div>
<div class="bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/30 dark:to-purple-800/30 p-5 rounded-xl border border-purple-200 dark:border-purple-700">
<div class="bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/30 dark:to-purple-800/30 p-4 rounded-xl border border-purple-200 dark:border-purple-700">
<NText
depth="3"
class="text-[1.5rem] sm:text-[1.6rem] font-medium text-purple-700 dark:text-purple-300"
class="text-[1.3rem] sm:text-[1.4rem] font-medium text-purple-700 dark:text-purple-300"
>
</NText>
<div class="mt-3 font-bold text-[1.7rem] sm:text-[1.8rem]">
<div class="mt-2 font-bold text-[1.5rem] sm:text-[1.6rem]">
<span
style={{
color:
@@ -427,14 +407,14 @@ export default defineComponent({
</span>
</div>
</div>
<div class="bg-gradient-to-br from-orange-50 to-orange-100 dark:from-orange-900/30 dark:to-orange-800/30 p-5 rounded-xl border border-orange-200 dark:border-orange-700">
<div class="bg-gradient-to-br from-orange-50 to-orange-100 dark:from-orange-900/30 dark:to-orange-800/30 p-4 rounded-xl border border-orange-200 dark:border-orange-700">
<NText
depth="3"
class="text-[1.5rem] sm:text-[1.6rem] font-medium text-orange-700 dark:text-orange-300"
class="text-[1.3rem] sm:text-[1.4rem] font-medium text-orange-700 dark:text-orange-300"
>
</NText>
<div class="mt-3 font-bold text-[1.7rem] sm:text-[1.8rem] uppercase">
<div class="mt-2 font-bold text-[1.5rem] sm:text-[1.6rem] uppercase">
{detailData.value?.monitor_type || '-'}
</div>
</div>
@@ -443,24 +423,24 @@ export default defineComponent({
{/* 监控配置信息 */}
<div>
<h4 class="font-semibold mb-6 text-primary text-[1.9rem] sm:text-[2rem] border-b-2 border-primary/20 pb-3">
<h4 class="font-semibold mb-4 text-primary text-[1.6rem] sm:text-[1.7rem] border-b-2 border-primary/20 pb-2">
</h4>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="space-y-5">
<div class="bg-gray-50 dark:bg-gray-800/50 p-5 rounded-lg">
<NText depth="3" class="text-[1.5rem] sm:text-[1.6rem] font-medium">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div class="space-y-4">
<div class="bg-gray-50 dark:bg-gray-800/50 p-4 rounded-lg">
<NText depth="3" class="text-[1.3rem] sm:text-[1.4rem] font-medium">
</NText>
<div class="mt-3 font-medium text-[1.6rem] sm:text-[1.7rem] break-words">
<div class="mt-2 font-medium text-[1.4rem] sm:text-[1.5rem] break-words">
{detailData.value?.name || '-'}
</div>
</div>
<div class="bg-gray-50 dark:bg-gray-800/50 p-5 rounded-lg">
<NText depth="3" class="text-[1.5rem] sm:text-[1.6rem] font-medium">
<div class="bg-gray-50 dark:bg-gray-800/50 p-4 rounded-lg">
<NText depth="3" class="text-[1.3rem] sm:text-[1.4rem] font-medium">
</NText>
<div class="mt-3 font-medium text-[1.6rem] sm:text-[1.7rem]">
<div class="mt-2 font-medium text-[1.4rem] sm:text-[1.5rem]">
<a
href={`https://${detailData.value?.target}`}
target="_blank"
@@ -472,29 +452,29 @@ export default defineComponent({
</div>
</div>
</div>
<div class="space-y-5">
<div class="bg-gray-50 dark:bg-gray-800/50 p-5 rounded-lg">
<NText depth="3" class="text-[1.5rem] sm:text-[1.6rem] font-medium">
<div class="space-y-4">
<div class="bg-gray-50 dark:bg-gray-800/50 p-4 rounded-lg">
<NText depth="3" class="text-[1.3rem] sm:text-[1.4rem] font-medium">
</NText>
<div class="mt-3 font-medium text-[1.6rem] sm:text-[1.7rem]">
<div class="mt-2 font-medium text-[1.4rem] sm:text-[1.5rem]">
{detailData.value?.ca || '-'}
</div>
</div>
<div class="bg-gray-50 dark:bg-gray-800/50 p-5 rounded-lg">
<NText depth="3" class="text-[1.5rem] sm:text-[1.6rem] font-medium">
<div class="bg-gray-50 dark:bg-gray-800/50 p-4 rounded-lg">
<NText depth="3" class="text-[1.3rem] sm:text-[1.4rem] font-medium">
</NText>
<div class="mt-3 font-medium text-[1.6rem] sm:text-[1.7rem] break-words">
<div class="mt-2 font-medium text-[1.4rem] sm:text-[1.5rem] break-words">
{formatDateTime(detailData.value?.last_time || '')}
</div>
</div>
{detailData.value?.tls_version && (
<div class="bg-gray-50 dark:bg-gray-800/50 p-5 rounded-lg">
<NText depth="3" class="text-[1.5rem] sm:text-[1.6rem] font-medium">
<div class="bg-gray-50 dark:bg-gray-800/50 p-4 rounded-lg">
<NText depth="3" class="text-[1.3rem] sm:text-[1.4rem] font-medium">
TLS版本
</NText>
<div class="mt-3 font-medium text-[1.6rem] sm:text-[1.7rem]">
<div class="mt-2 font-medium text-[1.4rem] sm:text-[1.5rem]">
{detailData.value.tls_version}
</div>
</div>
@@ -505,29 +485,29 @@ export default defineComponent({
{/* 证书基本信息 */}
<div>
<h4 class="font-semibold mb-6 text-success text-[1.9rem] sm:text-[2rem] border-b-2 border-success/20 pb-3">
<h4 class="font-semibold mb-4 text-success text-[1.6rem] sm:text-[1.7rem] border-b-2 border-success/20 pb-2">
</h4>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="bg-green-50 dark:bg-green-900/20 p-6 rounded-xl border border-green-200 dark:border-green-800">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div class="bg-green-50 dark:bg-green-900/20 p-4 rounded-xl border border-green-200 dark:border-green-800">
<NText
depth="3"
class="text-[1.5rem] sm:text-[1.6rem] font-medium text-green-700 dark:text-green-400"
class="text-[1.3rem] sm:text-[1.4rem] font-medium text-green-700 dark:text-green-400"
>
(CN)
</NText>
<div class="mt-3 font-mono text-[1.6rem] sm:text-[1.7rem] text-green-800 dark:text-green-300 break-all leading-relaxed">
<div class="mt-2 font-mono text-[1.4rem] sm:text-[1.5rem] text-green-800 dark:text-green-300 break-all leading-relaxed">
{detailData.value?.common_name || '-'}
</div>
</div>
<div class="bg-blue-50 dark:bg-blue-900/20 p-6 rounded-xl border border-blue-200 dark:border-blue-800">
<div class="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-xl border border-blue-200 dark:border-blue-800">
<NText
depth="3"
class="text-[1.5rem] sm:text-[1.6rem] font-medium text-blue-700 dark:text-blue-400"
class="text-[1.3rem] sm:text-[1.4rem] font-medium text-blue-700 dark:text-blue-400"
>
(SAN)
</NText>
<div class="mt-3 font-mono text-[1.6rem] sm:text-[1.7rem] text-blue-800 dark:text-blue-300 break-all leading-relaxed">
<div class="mt-2 font-mono text-[1.4rem] sm:text-[1.5rem] text-blue-800 dark:text-blue-300 break-all leading-relaxed">
{detailData.value?.sans || '-'}
</div>
</div>
@@ -536,40 +516,40 @@ export default defineComponent({
{/* 有效期详情 */}
<div>
<h4 class="font-semibold mb-6 text-success text-[1.9rem] sm:text-[2rem] border-b-2 border-success/20 pb-3">
<h4 class="font-semibold mb-4 text-success text-[1.6rem] sm:text-[1.7rem] border-b-2 border-success/20 pb-2">
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div class="bg-gradient-to-br from-green-50 to-emerald-50 dark:from-green-900/30 dark:to-emerald-900/30 p-6 rounded-xl border border-green-200 dark:border-green-700">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="bg-gradient-to-br from-green-50 to-emerald-50 dark:from-green-900/30 dark:to-emerald-900/30 p-4 rounded-xl border border-green-200 dark:border-green-700">
<NText
depth="3"
class="text-[1.5rem] sm:text-[1.6rem] font-medium text-green-700 dark:text-green-300"
class="text-[1.3rem] sm:text-[1.4rem] font-medium text-green-700 dark:text-green-300"
>
</NText>
<div class="mt-3 font-mono text-[1.6rem] sm:text-[1.7rem] text-green-600 dark:text-green-400 break-words">
<div class="mt-2 font-mono text-[1.4rem] sm:text-[1.5rem] text-green-600 dark:text-green-400 break-words">
{formatDateTime(detailData.value?.not_before || '')}
</div>
</div>
<div class="bg-gradient-to-br from-orange-50 to-red-50 dark:from-orange-900/30 dark:to-red-900/30 p-6 rounded-xl border border-orange-200 dark:border-orange-700">
<div class="bg-gradient-to-br from-orange-50 to-red-50 dark:from-orange-900/30 dark:to-red-900/30 p-4 rounded-xl border border-orange-200 dark:border-orange-700">
<NText
depth="3"
class="text-[1.5rem] sm:text-[1.6rem] font-medium text-orange-700 dark:text-orange-300"
class="text-[1.3rem] sm:text-[1.4rem] font-medium text-orange-700 dark:text-orange-300"
>
</NText>
<div class="mt-3 font-mono text-[1.6rem] sm:text-[1.7rem] text-orange-600 dark:text-orange-400 break-words">
<div class="mt-2 font-mono text-[1.4rem] sm:text-[1.5rem] text-orange-600 dark:text-orange-400 break-words">
{formatDateTime(detailData.value?.not_after || '')}
</div>
</div>
<div class="bg-gradient-to-br from-purple-50 to-indigo-50 dark:from-purple-900/30 dark:to-indigo-900/30 p-6 rounded-xl border border-purple-200 dark:border-purple-700 md:col-span-2 lg:col-span-1">
<div class="bg-gradient-to-br from-purple-50 to-indigo-50 dark:from-purple-900/30 dark:to-indigo-900/30 p-4 rounded-xl border border-purple-200 dark:border-purple-700 md:col-span-2 lg:col-span-1">
<NText
depth="3"
class="text-[1.5rem] sm:text-[1.6rem] font-medium text-purple-700 dark:text-purple-300"
class="text-[1.3rem] sm:text-[1.4rem] font-medium text-purple-700 dark:text-purple-300"
>
</NText>
<div class="mt-3 font-bold text-[1.7rem] sm:text-[1.8rem]">
<div class="mt-2 font-bold text-[1.5rem] sm:text-[1.6rem]">
{detailData.value && (
<span style={{ color: getDaysLeftColor(detailData.value.days_left) }}>
{detailData.value.days_left}
@@ -578,11 +558,11 @@ export default defineComponent({
</div>
</div>
</div>
<div class="mt-6 bg-gray-50 dark:bg-gray-800/50 p-5 rounded-lg">
<NText depth="3" class="text-[1.5rem] sm:text-[1.6rem] font-medium">
<div class="mt-4 bg-gray-50 dark:bg-gray-800/50 p-4 rounded-lg">
<NText depth="3" class="text-[1.3rem] sm:text-[1.4rem] font-medium">
</NText>
<div class="mt-3 font-medium text-[1.6rem] sm:text-[1.7rem] break-words">
<div class="mt-2 font-medium text-[1.4rem] sm:text-[1.5rem] break-words">
{formatValidityPeriod(
detailData.value?.not_before || '',
detailData.value?.not_after || '',
@@ -594,11 +574,11 @@ export default defineComponent({
{/* 证书链路信息 - 视觉增强 */}
{detailData.value?.cert_chain && (
<div>
<h4 class="font-semibold mb-6 text-success text-[1.9rem] sm:text-[2rem] border-b-2 border-success/20 pb-3">
<h4 class="font-semibold mb-4 text-success text-[1.6rem] sm:text-[1.7rem] border-b-2 border-success/20 pb-2">
</h4>
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 p-8 rounded-xl border border-blue-200 dark:border-blue-800">
<div class="space-y-5">{renderCertChainNode(detailData.value.cert_chain)}</div>
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 p-5 rounded-xl border border-blue-200 dark:border-blue-800">
<div class="space-y-4">{renderCertChainNode(detailData.value.cert_chain)}</div>
</div>
</div>
)}
@@ -606,11 +586,11 @@ export default defineComponent({
{/* 验证错误信息 */}
{detailData.value?.verify_error && (
<div>
<h4 class="font-semibold mb-6 text-error text-[1.9rem] sm:text-[2rem] border-b-2 border-error/20 pb-3">
<h4 class="font-semibold mb-4 text-error text-[1.6rem] sm:text-[1.7rem] border-b-2 border-error/20 pb-2">
</h4>
<div class="bg-red-50 dark:bg-red-900/20 p-6 rounded-xl border border-red-200 dark:border-red-800">
<div class="text-red-600 dark:text-red-300 text-[1.6rem] sm:text-[1.7rem] leading-relaxed break-words">
<div class="bg-red-50 dark:bg-red-900/20 p-4 rounded-xl border border-red-200 dark:border-red-800">
<div class="text-red-600 dark:text-red-300 text-[1.4rem] sm:text-[1.5rem] leading-relaxed break-words">
{detailData.value.verify_error}
</div>
</div>
@@ -626,11 +606,11 @@ export default defineComponent({
</div>
) : (
!loading.value && (
<NCard bordered class="text-center [&_.n-empty__description]:text-[1.6rem]">
<NCard bordered class="text-center [&_.n-empty__description]:text-[1.4rem]">
<NEmpty description="未找到监控详情数据" size="large">
{{
extra: () => (
<NButton type="primary" class="text-[1.4rem]" onClick={goBack}>
<NButton type="primary" class="text-[1.3rem]" onClick={goBack}>
</NButton>
),

View File

@@ -1,11 +1,10 @@
import { defineComponent, ref, computed } from 'vue'
import { NTabs, NTabPane, NUpload, NUploadDragger, NButton, NSpace, NText, NIcon, NCard, NDivider } from 'naive-ui'
import { CloudUploadOutline, DocumentOutline, DownloadOutline } from '@vicons/ionicons5'
import { CloudUploadOutline, DocumentOutline, DownloadOutline, CheckmarkCircleOutline } from '@vicons/ionicons5'
import { $t } from '@locales/index'
import { useMessage } from '@baota/naive-ui/hooks'
import { useError } from '@baota/hooks/error'
import { fileImportMonitor, downloadMonitorTemplate } from '@/api/monitor'
import type { UploadFileInfo } from 'naive-ui'
import type { SupportedFileType, FileUploadStatus } from '@/types/monitor'
@@ -24,6 +23,9 @@ export default defineComponent({
// 当前激活的标签页
const activeTab = ref<'import' | 'template'>('import')
// 选中的文件
const selectedFile = ref<File | null>(null)
// 文件上传状态
const uploadStatus = ref<FileUploadStatus>({
uploading: false,
@@ -47,9 +49,9 @@ export default defineComponent({
}
/**
* 处理文件上传前的验证
* 处理文件选择
*/
const handleBeforeUpload = (data: { file: UploadFileInfo; fileList: UploadFileInfo[] }): boolean => {
const handleFileSelect = (data: { file: UploadFileInfo; fileList: UploadFileInfo[] }): boolean => {
const file = data.file.file
if (!file) return false
@@ -65,18 +67,39 @@ export default defineComponent({
return false
}
return true
// 保存选中的文件,但不立即上传
selectedFile.value = file
// 重置上传状态
uploadStatus.value = {
uploading: false,
progress: 0,
success: false,
}
return false // 阻止自动上传
}
/**
* 处理文件上传
* 重置文件选择
*/
const handleFileUpload = async (options: {
file: UploadFileInfo
onProgress: (e: { percent: number }) => void
}) => {
const file = options.file.file
if (!file) return
const resetFileSelection = () => {
selectedFile.value = null
uploadStatus.value = {
uploading: false,
progress: 0,
success: false,
}
}
/**
* 确认上传文件
*/
const handleConfirmUpload = async () => {
if (!selectedFile.value) {
message.error('请先选择文件')
return
}
try {
uploadStatus.value = {
@@ -89,13 +112,12 @@ export default defineComponent({
const progressInterval = setInterval(() => {
if (uploadStatus.value.progress < 90) {
uploadStatus.value.progress += 10
options.onProgress({ percent: uploadStatus.value.progress })
}
}, 200)
// 创建FormData并上传文件
const formData = new FormData()
formData.append('file', file)
formData.append('file', selectedFile.value)
// 使用原生fetch进行文件上传因为useApi可能不支持FormData
const response = await fetch('/v1/monitor/file_add_monitor', {
@@ -117,8 +139,6 @@ export default defineComponent({
success: true,
}
options.onProgress({ percent: 100 })
// 显示上传结果
if (result.data) {
const { success_count, failed_count } = result.data
@@ -127,13 +147,17 @@ export default defineComponent({
.replace('{success}', success_count.toString())
.replace('{failed}', failed_count.toString()),
)
// 通知父组件刷新数据
emit('success')
} else {
message.success($t('t_4_1752724142308'))
emit('success')
}
// 通知父组件刷新数据
emit('success')
// 延迟重置状态,让用户看到成功状态
setTimeout(() => {
resetFileSelection()
}, 2000)
} catch (error) {
uploadStatus.value = {
uploading: false,
@@ -192,9 +216,17 @@ export default defineComponent({
if (uploadStatus.value.error) {
return uploadStatus.value.error
}
if (selectedFile.value) {
return `已选择文件: ${selectedFile.value.name}`
}
return $t('t_10_1752724143320')
})
// 计算是否可以上传
const canUpload = computed(() => {
return selectedFile.value && !uploadStatus.value.uploading
})
return () => (
<div class="import-monitor-modal">
<NTabs value={activeTab.value} onUpdateValue={(value) => (activeTab.value = value as 'import' | 'template')}>
@@ -206,21 +238,56 @@ export default defineComponent({
multiple={false}
accept=".txt,.csv,.json,.xlsx"
showFileList={false}
onBeforeUpload={handleBeforeUpload}
customRequest={handleFileUpload}
onBeforeUpload={handleFileSelect}
>
<NUploadDragger class="min-h-[200px]">
<div class="text-center">
<NIcon size={48} class="text-primary mb-4">
<CloudUploadOutline />
<NIcon size={48} class={`mb-4 ${uploadStatus.value.success ? 'text-green-500' : 'text-primary'}`}>
{uploadStatus.value.success ? <CheckmarkCircleOutline /> : <CloudUploadOutline />}
</NIcon>
<NText class="text-lg block mb-2">{uploadTipText.value}</NText>
<NText depth="3" class="text-sm">
{$t('t_13_1752724148548')}
{selectedFile.value ? '点击重新选择文件或拖拽新文件到此区域' : $t('t_13_1752724148548')}
</NText>
</div>
</NUploadDragger>
</NUpload>
{/* 上传进度条 */}
{uploadStatus.value.uploading && (
<div class="mt-4">
<NText class="text-sm text-gray-500 mb-2">: {uploadStatus.value.progress}%</NText>
<div class="w-full bg-gray-200 rounded-full h-2">
<div
class="bg-primary h-2 rounded-full transition-all duration-300"
style={{ width: `${uploadStatus.value.progress}%` }}
></div>
</div>
</div>
)}
{/* 操作按钮 */}
<div class="mt-4 flex justify-center gap-3">
{selectedFile.value && !uploadStatus.value.success && (
<NButton
type="default"
size="large"
disabled={uploadStatus.value.uploading}
onClick={resetFileSelection}
>
</NButton>
)}
<NButton
type="primary"
size="large"
loading={uploadStatus.value.uploading}
disabled={!canUpload.value}
onClick={handleConfirmUpload}
>
{uploadStatus.value.uploading ? '上传中...' : uploadStatus.value.success ? '上传成功' : '确认上传'}
</NButton>
</div>
</NCard>
<NDivider />

View File

@@ -441,11 +441,23 @@ export const useMonitorFormController = (data: UpdateSiteMonitorParams | null =
const config = computed(() => [
useFormInput('名称', 'name'),
useFormInput('域名/IP地址', 'target'),
useFormSelect('协议类型', 'monitor_type', [
{ label: 'HTTPS', value: 'https' },
{ label: 'SMTP', value: 'smtp' },
]),
useFormInputNumber('周期(分钟)', 'cycle', { class: 'w-full' }),
useFormSelect(
'协议类型',
'monitor_type',
[
{ label: 'HTTPS', value: 'https' },
{ label: 'SMTP', value: 'smtp' },
],
{
disabled: data !== null, // 编辑模式下禁用协议类型选择
},
),
useFormInputNumber('周期(分钟)', 'cycle', {
class: 'w-full',
min: 1,
max: 1440,
precision: 0, // 确保只接受整数
}),
useFormCustom(() => {
// 确保 report_types 是数组格式
const currentValue = Array.isArray(monitorForm.value.report_types)
@@ -471,9 +483,14 @@ export const useMonitorFormController = (data: UpdateSiteMonitorParams | null =
}),
// 到期提醒设置分组
createGroupTitle('到期提醒设置'),
useFormInputNumber('提前天数', 'advance_day', { class: 'w-full', min: 1, max: 365 }),
useFormInputNumber('提前天数', 'advance_day', {
class: 'w-full',
min: 1,
max: 365,
precision: 0, // 确保只接受整数
}),
useFormCustom(() => {
const advanceDay = monitorForm.value.advance_day || 90
const advanceDay = monitorForm.value.advance_day || 30
return (
<NText
depth="3"
@@ -485,7 +502,12 @@ export const useMonitorFormController = (data: UpdateSiteMonitorParams | null =
}),
// 连续失败通知设置分组
createGroupTitle('连续失败通知设置'),
useFormInputNumber('重复发送间隔(次数)', 'repeat_send_gap', { class: 'w-full', min: 1, max: 100 }),
useFormInputNumber('重复发送间隔(次数)', 'repeat_send_gap', {
class: 'w-full',
min: 1,
max: 100,
precision: 0, // 确保只接受整数
}),
useFormSwitch('启用状态', 'active', {
checkedValue: 1,
uncheckedValue: 0,
@@ -510,7 +532,21 @@ export const useMonitorFormController = (data: UpdateSiteMonitorParams | null =
},
},
monitor_type: { required: true, message: '请选择协议类型', trigger: 'change' },
cycle: { required: true, message: '请输入周期', trigger: 'input', type: 'number', min: 1, max: 365 },
cycle: {
required: true,
message: '请输入周期(1-1440分钟)',
trigger: 'input',
type: 'number',
min: 1,
max: 1440,
validator: (_rule: any, value: any, callback: any) => {
if (value !== undefined && value !== null && !Number.isInteger(Number(value))) {
callback(new Error('周期必须为整数'))
} else {
callback()
}
},
},
report_types: {
required: true,
message: '请选择消息通知类型',
@@ -523,14 +559,35 @@ export const useMonitorFormController = (data: UpdateSiteMonitorParams | null =
}
},
},
advance_day: { required: true, message: '请输入提前天数', trigger: 'input', type: 'number', min: 1, max: 365 },
advance_day: {
required: true,
message: '请输入提前天数(1-365天)',
trigger: 'input',
type: 'number',
min: 1,
max: 365,
validator: (_rule: any, value: any, callback: any) => {
if (value !== undefined && value !== null && !Number.isInteger(Number(value))) {
callback(new Error('提前天数必须为整数'))
} else {
callback()
}
},
},
repeat_send_gap: {
required: true,
message: '请输入重复发送间隔',
message: '请输入重复发送间隔(1-100次)',
trigger: 'input',
type: 'number',
min: 1,
max: 100,
validator: (_rule: any, value: any, callback: any) => {
if (value !== undefined && value !== null && !Number.isInteger(Number(value))) {
callback(new Error('重复发送间隔必须为整数'))
} else {
callback()
}
},
},
active: { required: true, message: '请选择启用状态', trigger: 'change', type: 'number' },
} as FormRules

View File

@@ -57,7 +57,7 @@ export const useMonitorStore = defineStore('monitor-store', (): MonitorStoreExpo
cycle: 1,
repeat_send_gap: 10, // 默认重复发送间隔10次
active: 1, // 默认启用状态
advance_day: 90, // 默认提前90天
advance_day: 30, // 默认提前30天
})
// -------------------- 方法定义 --------------------
@@ -213,7 +213,7 @@ export const useMonitorStore = defineStore('monitor-store', (): MonitorStoreExpo
cycle: 1,
repeat_send_gap: 10, // 默认重复发送间隔10次
active: 1, // 默认启用状态
advance_day: 90, // 默认提前90天
advance_day: 30, // 默认提前30天
}
}