fix
This commit is contained in:
@@ -21,6 +21,7 @@ export interface IpScanTask {
|
|||||||
online_count?: number
|
online_count?: number
|
||||||
scan_count?: number
|
scan_count?: number
|
||||||
enable?: boolean
|
enable?: boolean
|
||||||
|
priority?: number
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@@ -41,9 +42,23 @@ export const fetchIpScanList = (params?: { page?: number; size?: number; keyword
|
|||||||
export const fetchIpScanDetail = (id: number) =>
|
export const fetchIpScanDetail = (id: number) =>
|
||||||
request.get<{ code: number; details?: IpScanTask; message?: string }>(`/DC-Control/v1/ipscans/${id}`)
|
request.get<{ code: number; details?: IpScanTask; message?: string }>(`/DC-Control/v1/ipscans/${id}`)
|
||||||
|
|
||||||
/** 触发一次扫描(会创建 scan_run 并调用 Agent) */
|
/** 触发一次扫描(会创建 scan_run 并调用 Agent);成功体见文档 { message: "scan triggered" } */
|
||||||
export const triggerIpScan = (id: number) =>
|
export const triggerIpScan = (id: number) =>
|
||||||
request.post<{ code: number; message?: string }>(`/DC-Control/v1/ipscans/${id}/trigger`)
|
request.post<{ code: number; details?: { message?: string }; message?: string }>(
|
||||||
|
`/DC-Control/v1/ipscans/${id}/trigger`,
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 启动扫描任务(文档:将 status 置为 running,与 Cron 调度语义相关) */
|
||||||
|
export const startIpScan = (id: number) =>
|
||||||
|
request.post<{ code: number; details?: { message?: string }; message?: string }>(
|
||||||
|
`/DC-Control/v1/ipscans/${id}/start`,
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 停止扫描任务(文档:将 status 置为 stopped) */
|
||||||
|
export const stopIpScan = (id: number) =>
|
||||||
|
request.post<{ code: number; details?: { message?: string }; message?: string }>(
|
||||||
|
`/DC-Control/v1/ipscans/${id}/stop`,
|
||||||
|
)
|
||||||
|
|
||||||
/** 创建扫描任务 */
|
/** 创建扫描任务 */
|
||||||
export const createIpScan = (data: Partial<IpScanTask>) =>
|
export const createIpScan = (data: Partial<IpScanTask>) =>
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ export interface ServerItem {
|
|||||||
collect_args: string
|
collect_args: string
|
||||||
collect_interval: number
|
collect_interval: number
|
||||||
collect_last_result: string
|
collect_last_result: string
|
||||||
|
is_ip_scan_server: boolean
|
||||||
|
ip_scan_port: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 服务器列表响应 */
|
/** 服务器列表响应 */
|
||||||
@@ -42,6 +44,7 @@ export interface ServerListParams {
|
|||||||
size?: number
|
size?: number
|
||||||
keyword?: string
|
keyword?: string
|
||||||
collect_on?: boolean
|
collect_on?: boolean
|
||||||
|
is_ip_scan_server?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建/更新服务器请求参数 */
|
/** 创建/更新服务器请求参数 */
|
||||||
@@ -66,6 +69,8 @@ export interface ServerFormData {
|
|||||||
collect_args?: string
|
collect_args?: string
|
||||||
collect_interval?: number
|
collect_interval?: number
|
||||||
collect_last_result?: string
|
collect_last_result?: string
|
||||||
|
is_ip_scan_server?: boolean
|
||||||
|
ip_scan_port?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取服务器列表(分页) */
|
/** 获取服务器列表(分页) */
|
||||||
|
|||||||
@@ -126,6 +126,19 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
|
||||||
|
<a-row :gutter="20">
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item field="is_ip_scan_server" label="IP扫描执行服务器">
|
||||||
|
<a-switch v-model="formData.is_ip_scan_server" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item v-if="formData.is_ip_scan_server" field="ip_scan_port" label="IP扫描端口">
|
||||||
|
<a-input-number v-model="formData.ip_scan_port" :min="1" :max="65535" placeholder="默认12429" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
<a-form-item field="description" label="描述信息">
|
<a-form-item field="description" label="描述信息">
|
||||||
<a-textarea
|
<a-textarea
|
||||||
v-model="formData.description"
|
v-model="formData.description"
|
||||||
@@ -178,6 +191,8 @@ const formData = reactive<ServerFormData>({
|
|||||||
status: 'unknown',
|
status: 'unknown',
|
||||||
collect_on: true,
|
collect_on: true,
|
||||||
collect_interval: 60,
|
collect_interval: 60,
|
||||||
|
is_ip_scan_server: false,
|
||||||
|
ip_scan_port: 12429,
|
||||||
})
|
})
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
@@ -185,6 +200,21 @@ const rules = {
|
|||||||
host: [{ required: true, message: '请输入主机地址' }],
|
host: [{ required: true, message: '请输入主机地址' }],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateAgentConfigURL(raw?: string): string | null {
|
||||||
|
const v = (raw || '').trim()
|
||||||
|
if (!v) return null
|
||||||
|
try {
|
||||||
|
const u = new URL(v)
|
||||||
|
const protocol = u.protocol.toLowerCase()
|
||||||
|
if (protocol === 'https:' && u.port && u.port !== '443') {
|
||||||
|
return `Agent 配置 URL 使用 https 且端口为 ${u.port},请确认该端口确实启用了 TLS;若为明文服务请改为 http://`
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} catch {
|
||||||
|
return 'Agent 配置 URL 格式不合法,请输入完整 http(s) URL'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(val) => {
|
(val) => {
|
||||||
@@ -208,6 +238,8 @@ watch(
|
|||||||
status: props.record.status || 'unknown',
|
status: props.record.status || 'unknown',
|
||||||
collect_on: props.record.collect_on ?? true,
|
collect_on: props.record.collect_on ?? true,
|
||||||
collect_interval: props.record.collect_interval || 60,
|
collect_interval: props.record.collect_interval || 60,
|
||||||
|
is_ip_scan_server: props.record.is_ip_scan_server ?? false,
|
||||||
|
ip_scan_port: props.record.ip_scan_port || 12429,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Object.assign(formData, {
|
Object.assign(formData, {
|
||||||
@@ -228,6 +260,8 @@ watch(
|
|||||||
status: 'unknown',
|
status: 'unknown',
|
||||||
collect_on: true,
|
collect_on: true,
|
||||||
collect_interval: 60,
|
collect_interval: 60,
|
||||||
|
is_ip_scan_server: false,
|
||||||
|
ip_scan_port: 12429,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,6 +272,12 @@ const handleOk = async () => {
|
|||||||
try {
|
try {
|
||||||
await formRef.value?.validate()
|
await formRef.value?.validate()
|
||||||
|
|
||||||
|
const agentConfigErr = validateAgentConfigURL(formData.agent_config)
|
||||||
|
if (agentConfigErr) {
|
||||||
|
Message.warning(agentConfigErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
confirmLoading.value = true
|
confirmLoading.value = true
|
||||||
|
|
||||||
const submitData: ServerFormData = {
|
const submitData: ServerFormData = {
|
||||||
@@ -258,6 +298,8 @@ const handleOk = async () => {
|
|||||||
status: formData.status,
|
status: formData.status,
|
||||||
collect_on: formData.collect_on,
|
collect_on: formData.collect_on,
|
||||||
collect_interval: formData.collect_interval,
|
collect_interval: formData.collect_interval,
|
||||||
|
is_ip_scan_server: formData.is_ip_scan_server,
|
||||||
|
ip_scan_port: formData.ip_scan_port,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEdit.value && props.record?.id) {
|
if (isEdit.value && props.record?.id) {
|
||||||
|
|||||||
@@ -14,7 +14,19 @@
|
|||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a-card :bordered="false">
|
<a-card :bordered="false" title="任务列表">
|
||||||
|
<template #extra>
|
||||||
|
<a-space>
|
||||||
|
<a-input
|
||||||
|
v-model="keyword"
|
||||||
|
allow-clear
|
||||||
|
placeholder="按名称搜索(keyword)"
|
||||||
|
style="width: 220px"
|
||||||
|
@press-enter="runSearch"
|
||||||
|
/>
|
||||||
|
<a-button type="primary" @click="runSearch">查询</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
<a-table
|
<a-table
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
@@ -32,9 +44,17 @@
|
|||||||
<template #status="{ record }">
|
<template #status="{ record }">
|
||||||
<a-tag bordered>{{ record.status || '—' }}</a-tag>
|
<a-tag bordered>{{ record.status || '—' }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
|
<template #latestError="{ record }">
|
||||||
|
<span v-if="latestErrorMap[record.id]" class="error-text" :title="latestErrorMap[record.id]">
|
||||||
|
{{ latestErrorMap[record.id] }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="text-muted">—</span>
|
||||||
|
</template>
|
||||||
<template #actions="{ record }">
|
<template #actions="{ record }">
|
||||||
<a-space>
|
<a-space wrap>
|
||||||
<a-button type="text" size="small" @click="openEdit(record)">编辑</a-button>
|
<a-button type="text" size="small" @click="openEdit(record)">编辑</a-button>
|
||||||
|
<a-button type="text" size="small" @click="handleStart(record)">启动</a-button>
|
||||||
|
<a-button type="text" size="small" @click="handleStop(record)">停止</a-button>
|
||||||
<a-button type="text" size="small" @click="handleTrigger(record)">触发扫描</a-button>
|
<a-button type="text" size="small" @click="handleTrigger(record)">触发扫描</a-button>
|
||||||
<a-popconfirm content="确定删除该任务?" @ok="handleDelete(record)">
|
<a-popconfirm content="确定删除该任务?" @ok="handleDelete(record)">
|
||||||
<a-button type="text" size="small" status="danger">删除</a-button>
|
<a-button type="text" size="small" status="danger">删除</a-button>
|
||||||
@@ -101,6 +121,19 @@
|
|||||||
<a-form-item label="Cron(可选)">
|
<a-form-item label="Cron(可选)">
|
||||||
<a-input v-model="form.cron_expr" placeholder="定期扫描 Cron 表达式" allow-clear />
|
<a-input v-model="form.cron_expr" placeholder="定期扫描 Cron 表达式" allow-clear />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="优先级">
|
||||||
|
<a-input-number v-model="form.priority" :min="0" :max="1000" style="width: 100%" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="扫描配置(JSON)" extra="须为合法 JSON;可不改,默认提交 {}">
|
||||||
|
<a-textarea
|
||||||
|
v-model="form.config"
|
||||||
|
placeholder="{}"
|
||||||
|
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="启用">
|
||||||
|
<a-switch v-model="formEnable" />
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label="描述">
|
<a-form-item label="描述">
|
||||||
<a-input v-model="form.description" allow-clear />
|
<a-input v-model="form.description" allow-clear />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -110,7 +143,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref, onMounted } from 'vue'
|
import { reactive, ref, computed, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { IconPlus } from '@arco-design/web-vue/es/icon'
|
import { IconPlus } from '@arco-design/web-vue/es/icon'
|
||||||
import { Message } from '@arco-design/web-vue'
|
import { Message } from '@arco-design/web-vue'
|
||||||
@@ -121,9 +154,12 @@ import {
|
|||||||
updateIpScan,
|
updateIpScan,
|
||||||
deleteIpScan,
|
deleteIpScan,
|
||||||
triggerIpScan,
|
triggerIpScan,
|
||||||
|
startIpScan,
|
||||||
|
stopIpScan,
|
||||||
type IpScanTask,
|
type IpScanTask,
|
||||||
} from '@/api/ops/ipScan'
|
} from '@/api/ops/ipScan'
|
||||||
import { fetchServerList, type ServerItem } from '@/api/ops/server'
|
import { fetchServerList, type ServerItem } from '@/api/ops/server'
|
||||||
|
import { fetchDiscoveryScanRuns, type DiscoveryScanRun } from '@/api/ops/discovery'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -132,6 +168,7 @@ const serversLoading = ref(false)
|
|||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const tableData = ref<IpScanTask[]>([])
|
const tableData = ref<IpScanTask[]>([])
|
||||||
const servers = ref<ServerItem[]>([])
|
const servers = ref<ServerItem[]>([])
|
||||||
|
const latestErrorMap = ref<Record<number, string>>({})
|
||||||
|
|
||||||
const pagination = ref({
|
const pagination = ref({
|
||||||
current: 1,
|
current: 1,
|
||||||
@@ -141,6 +178,7 @@ const pagination = ref({
|
|||||||
|
|
||||||
const modalVisible = ref(false)
|
const modalVisible = ref(false)
|
||||||
const editingId = ref<number | null>(null)
|
const editingId = ref<number | null>(null)
|
||||||
|
const keyword = ref('')
|
||||||
|
|
||||||
const defaultForm = (): Partial<IpScanTask> => ({
|
const defaultForm = (): Partial<IpScanTask> => ({
|
||||||
name: '',
|
name: '',
|
||||||
@@ -148,15 +186,48 @@ const defaultForm = (): Partial<IpScanTask> => ({
|
|||||||
description: '',
|
description: '',
|
||||||
target_range: '',
|
target_range: '',
|
||||||
port_range: '',
|
port_range: '',
|
||||||
|
config: '{}',
|
||||||
timeout: 5,
|
timeout: 5,
|
||||||
concurrency: 100,
|
concurrency: 100,
|
||||||
cron_expr: '',
|
cron_expr: '',
|
||||||
server_id: undefined,
|
server_id: undefined,
|
||||||
status: 'stopped',
|
status: 'stopped',
|
||||||
|
enable: true,
|
||||||
|
priority: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
const form = reactive<Partial<IpScanTask>>(defaultForm())
|
const form = reactive<Partial<IpScanTask>>(defaultForm())
|
||||||
|
|
||||||
|
/** 与 a-switch 绑定(form.enable 可能 undefined) */
|
||||||
|
const formEnable = computed({
|
||||||
|
get: () => form.enable !== false,
|
||||||
|
set: (v: boolean) => {
|
||||||
|
form.enable = v
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 表单中的 config 转为提交用字符串:空或仅空白 → "{}";否则须可 JSON.parse */
|
||||||
|
function configToSubmitJson(raw: string | undefined): { ok: true; value: string } | { ok: false } {
|
||||||
|
const s = raw?.trim() ?? ''
|
||||||
|
if (!s) return { ok: true, value: '{}' }
|
||||||
|
try {
|
||||||
|
return { ok: true, value: JSON.stringify(JSON.parse(s)) }
|
||||||
|
} catch {
|
||||||
|
return { ok: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 编辑回显:无配置或空串用 "{}";合法 JSON 则格式化便于阅读 */
|
||||||
|
function configForForm(raw?: string): string {
|
||||||
|
const s = raw?.trim() ?? ''
|
||||||
|
if (!s) return '{}'
|
||||||
|
try {
|
||||||
|
return JSON.stringify(JSON.parse(s), null, 2)
|
||||||
|
} catch {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function unwrapList(res: any): { total: number; data: IpScanTask[] } | undefined {
|
function unwrapList(res: any): { total: number; data: IpScanTask[] } | undefined {
|
||||||
if (!res || res.code !== 0) return undefined
|
if (!res || res.code !== 0) return undefined
|
||||||
const d = res.details ?? res.data
|
const d = res.details ?? res.data
|
||||||
@@ -167,7 +238,7 @@ function unwrapList(res: any): { total: number; data: IpScanTask[] } | undefined
|
|||||||
const loadServers = async () => {
|
const loadServers = async () => {
|
||||||
serversLoading.value = true
|
serversLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res: any = await fetchServerList({ page: 1, size: 500 })
|
const res: any = await fetchServerList({ page: 1, size: 500, is_ip_scan_server: true })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const d = res.details || {}
|
const d = res.details || {}
|
||||||
servers.value = d.data || []
|
servers.value = d.data || []
|
||||||
@@ -183,10 +254,12 @@ const loadTable = async () => {
|
|||||||
const res: any = await fetchIpScanList({
|
const res: any = await fetchIpScanList({
|
||||||
page: pagination.value.current,
|
page: pagination.value.current,
|
||||||
size: pagination.value.pageSize,
|
size: pagination.value.pageSize,
|
||||||
|
keyword: keyword.value.trim() || undefined,
|
||||||
})
|
})
|
||||||
const page = unwrapList(res)
|
const page = unwrapList(res)
|
||||||
tableData.value = page?.data || []
|
tableData.value = page?.data || []
|
||||||
pagination.value.total = page?.total || 0
|
pagination.value.total = page?.total || 0
|
||||||
|
await loadLatestErrorsForCurrentPage()
|
||||||
if (res.code !== 0) {
|
if (res.code !== 0) {
|
||||||
Message.error(res.message || '加载失败')
|
Message.error(res.message || '加载失败')
|
||||||
}
|
}
|
||||||
@@ -195,6 +268,31 @@ const loadTable = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadLatestErrorsForCurrentPage = async () => {
|
||||||
|
const rows = tableData.value || []
|
||||||
|
if (!rows.length) {
|
||||||
|
latestErrorMap.value = {}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const entries = await Promise.all(
|
||||||
|
rows.map(async (row) => {
|
||||||
|
try {
|
||||||
|
const res: any = await fetchDiscoveryScanRuns({ scan_id: row.id, page: 1, size: 1 })
|
||||||
|
if (res?.code !== 0) return [row.id, ''] as const
|
||||||
|
const d = (res.details ?? res.data) as { data?: DiscoveryScanRun[] } | undefined
|
||||||
|
const last = d?.data?.[0]
|
||||||
|
if (last?.status === 'failed' && last.error_message) {
|
||||||
|
return [row.id, last.error_message] as const
|
||||||
|
}
|
||||||
|
return [row.id, ''] as const
|
||||||
|
} catch {
|
||||||
|
return [row.id, ''] as const
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
latestErrorMap.value = Object.fromEntries(entries)
|
||||||
|
}
|
||||||
|
|
||||||
const serverLabel = (id?: number) => {
|
const serverLabel = (id?: number) => {
|
||||||
if (!id) return '—'
|
if (!id) return '—'
|
||||||
const s = servers.value.find((x) => x.id === id)
|
const s = servers.value.find((x) => x.id === id)
|
||||||
@@ -206,6 +304,11 @@ const onPageChange = (current: number) => {
|
|||||||
loadTable()
|
loadTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const runSearch = () => {
|
||||||
|
pagination.value.current = 1
|
||||||
|
loadTable()
|
||||||
|
}
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
Object.assign(form, defaultForm())
|
Object.assign(form, defaultForm())
|
||||||
}
|
}
|
||||||
@@ -224,11 +327,14 @@ const openEdit = (row: IpScanTask) => {
|
|||||||
description: row.description || '',
|
description: row.description || '',
|
||||||
target_range: row.target_range,
|
target_range: row.target_range,
|
||||||
port_range: row.port_range || '',
|
port_range: row.port_range || '',
|
||||||
|
config: configForForm(row.config),
|
||||||
timeout: row.timeout ?? 5,
|
timeout: row.timeout ?? 5,
|
||||||
concurrency: row.concurrency ?? 100,
|
concurrency: row.concurrency ?? 100,
|
||||||
cron_expr: row.cron_expr || '',
|
cron_expr: row.cron_expr || '',
|
||||||
server_id: row.server_id,
|
server_id: row.server_id,
|
||||||
status: row.status || 'stopped',
|
status: row.status || 'stopped',
|
||||||
|
enable: row.enable !== false,
|
||||||
|
priority: row.priority ?? 0,
|
||||||
})
|
})
|
||||||
modalVisible.value = true
|
modalVisible.value = true
|
||||||
}
|
}
|
||||||
@@ -255,6 +361,12 @@ const submitForm = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cfg = configToSubmitJson(form.config)
|
||||||
|
if (!cfg.ok) {
|
||||||
|
Message.warning('扫描配置须为合法 JSON;不填时请保留 {}')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
const body: Partial<IpScanTask> = {
|
const body: Partial<IpScanTask> = {
|
||||||
@@ -263,11 +375,13 @@ const submitForm = async () => {
|
|||||||
description: form.description,
|
description: form.description,
|
||||||
target_range: form.target_range.trim(),
|
target_range: form.target_range.trim(),
|
||||||
port_range: form.port_range,
|
port_range: form.port_range,
|
||||||
|
config: cfg.value,
|
||||||
timeout: form.timeout,
|
timeout: form.timeout,
|
||||||
concurrency: form.concurrency,
|
concurrency: form.concurrency,
|
||||||
cron_expr: form.cron_expr,
|
cron_expr: form.cron_expr,
|
||||||
server_id: form.server_id,
|
server_id: form.server_id,
|
||||||
enable: true,
|
enable: form.enable !== false,
|
||||||
|
priority: form.priority ?? 0,
|
||||||
}
|
}
|
||||||
if (!editingId.value) {
|
if (!editingId.value) {
|
||||||
body.status = 'stopped'
|
body.status = 'stopped'
|
||||||
@@ -317,7 +431,8 @@ const handleTrigger = async (row: IpScanTask) => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await triggerIpScan(row.id)
|
const res: any = await triggerIpScan(row.id)
|
||||||
if (res?.code === 0) {
|
if (res?.code === 0) {
|
||||||
Message.success('已触发扫描')
|
const msg = res?.details?.message || res?.data?.message
|
||||||
|
Message.success(msg === 'scan triggered' ? '已触发扫描' : msg || '已触发扫描')
|
||||||
} else {
|
} else {
|
||||||
Message.error(res?.message || '触发失败')
|
Message.error(res?.message || '触发失败')
|
||||||
}
|
}
|
||||||
@@ -327,6 +442,36 @@ const handleTrigger = async (row: IpScanTask) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleStart = async (row: IpScanTask) => {
|
||||||
|
try {
|
||||||
|
const res: any = await startIpScan(row.id)
|
||||||
|
if (res?.code === 0) {
|
||||||
|
Message.success('已启动任务')
|
||||||
|
await loadTable()
|
||||||
|
} else {
|
||||||
|
Message.error(res?.message || '启动失败')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
Message.error('启动失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStop = async (row: IpScanTask) => {
|
||||||
|
try {
|
||||||
|
const res: any = await stopIpScan(row.id)
|
||||||
|
if (res?.code === 0) {
|
||||||
|
Message.success('已停止任务')
|
||||||
|
await loadTable()
|
||||||
|
} else {
|
||||||
|
Message.error(res?.message || '停止失败')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
Message.error('停止失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const goAutoTopology = () => {
|
const goAutoTopology = () => {
|
||||||
router.push({ path: '/netarch/auto-topo' })
|
router.push({ path: '/netarch/auto-topo' })
|
||||||
}
|
}
|
||||||
@@ -338,7 +483,8 @@ const columns: TableColumnData[] = [
|
|||||||
{ title: '目标范围', slotName: 'target', minWidth: 200 },
|
{ title: '目标范围', slotName: 'target', minWidth: 200 },
|
||||||
{ title: 'Agent', slotName: 'server', width: 180, ellipsis: true, tooltip: true },
|
{ title: 'Agent', slotName: 'server', width: 180, ellipsis: true, tooltip: true },
|
||||||
{ title: '状态', slotName: 'status', width: 100 },
|
{ title: '状态', slotName: 'status', width: 100 },
|
||||||
{ title: '操作', slotName: 'actions', width: 220, fixed: 'right' },
|
{ title: '最近错误', slotName: 'latestError', minWidth: 260, ellipsis: true, tooltip: true },
|
||||||
|
{ title: '操作', slotName: 'actions', width: 300, fixed: 'right' },
|
||||||
]
|
]
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -384,4 +530,12 @@ export default {
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
color: #f53f3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: var(--color-text-3);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user