feat: v0.9.0 新增记录日志、设备更换记录及IMC服务集成
- 新增 RecordsLog.vue 操作记录日志页面 - 新增 ReplacementRecords.vue 设备更换记录页面 - 新增 imc_service.py IMC网管系统集成服务 - 新增 datetime.js 前端日期时间工具函数 - 新增 OLT时间同步.md 文档 - 扩展 devices.py API:设备更换记录、批量操作等 - 扩展 ssh_service.py:OLT时间同步功能 - 扩展 olt.py:新增时间同步相关接口 - 更新 DeviceList.vue:增强设备列表功能 - 更新路由和导航菜单 - 将 .claude/ 和 .mcp.json 加入 .gitignore Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,9 @@
|
||||
新增设备
|
||||
<el-badge v-if="newDeviceCount > 0" :value="newDeviceCount" style="margin-left: 4px" />
|
||||
</el-button>
|
||||
<el-button v-if="can('olt.manage')" type="info" plain size="small" @click="openNtpSync" :loading="ntpSyncing">
|
||||
同步NTP
|
||||
</el-button>
|
||||
<el-button v-if="can('olt.loopback')" type="danger" plain size="small" @click="runLoopbackDetection" :loading="loopDetecting">
|
||||
环路检测
|
||||
</el-button>
|
||||
@@ -257,6 +260,67 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- NTP 时间同步 -->
|
||||
<el-dialog v-model="ntpSyncVisible" title="NTP 时间服务器同步" width="560px" destroy-on-close>
|
||||
<template v-if="ntpPhase === 'config'">
|
||||
<p style="margin: 0 0 16px; color: var(--text-muted); font-size: 13px">
|
||||
批量对所有 OLT 执行 NTP 时间服务器配置,并强制保存。旧服务器若不存在会自动忽略。
|
||||
</p>
|
||||
<el-form label-width="110px">
|
||||
<el-form-item label="旧 NTP 服务器">
|
||||
<el-input v-model="ntpForm.old_server" class="mono-val" placeholder="172.16.0.254" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新 NTP 服务器">
|
||||
<el-input v-model="ntpForm.new_server" class="mono-val" placeholder="172.16.1.252" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div style="background: var(--bg-subtle); border: 1px solid var(--border-subtle); border-radius: 6px; padding: 10px 14px; font-size: 12px; color: var(--text-muted); font-family: monospace; line-height: 1.7">
|
||||
<div>system-view</div>
|
||||
<div>undo ntp-service unicast-server {{ ntpForm.old_server }}</div>
|
||||
<div>ntp-service unicast-server {{ ntpForm.new_server }}</div>
|
||||
<div>clock timezone Beijing add 08:00:00</div>
|
||||
<div>quit</div>
|
||||
<div>save force</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="result-stats" style="margin-bottom: 16px">
|
||||
<div class="result-stat">
|
||||
<span class="rs-val" style="color: var(--success)">{{ ntpSyncResult.success || 0 }}</span>
|
||||
<span class="rs-label">成功</span>
|
||||
</div>
|
||||
<div class="result-stat">
|
||||
<span class="rs-val offline">{{ ntpSyncResult.failed || 0 }}</span>
|
||||
<span class="rs-label">失败</span>
|
||||
</div>
|
||||
<div class="result-stat">
|
||||
<span class="rs-val muted">{{ ntpSyncResult.total || 0 }}</span>
|
||||
<span class="rs-label">共计</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="ntpSyncResult.results || []" border size="small" max-height="320">
|
||||
<el-table-column prop="olt_location" label="OLT" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="olt_ip" label="IP" width="140">
|
||||
<template #default="{ row }"><span class="mono-val">{{ row.olt_ip }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="72" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.success ? 'success' : 'danger'" size="small">{{ row.success ? '成功' : '失败' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="error" label="错误信息" min-width="130" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span style="color: var(--danger); font-size: 12px">{{ row.error || '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="ntpSyncVisible = false">关闭</el-button>
|
||||
<el-button v-if="ntpPhase === 'config'" type="primary" @click="runNtpSync" :loading="ntpSyncing">开始同步</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 添加/编辑 OLT -->
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑 OLT 设备' : '添加 OLT 设备'" width="480px" destroy-on-close>
|
||||
<el-form :model="form" label-width="90px">
|
||||
@@ -443,7 +507,7 @@
|
||||
<el-table-column label="最后发现" width="155">
|
||||
<template #default="{ row }">
|
||||
<span style="font-size: 12px; color: var(--text-muted)">
|
||||
{{ row.last_seen_at ? new Date(row.last_seen_at).toLocaleString() : '—' }}
|
||||
{{ fmtTime(row.last_seen_at) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -508,6 +572,7 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElMessage } from '../utils/message'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { fmtTime } from '../utils/datetime'
|
||||
import request from '../utils/request'
|
||||
import { useMobile } from '../composables/useMobile'
|
||||
import { usePermission } from '../composables/usePermission'
|
||||
@@ -941,6 +1006,31 @@ const openPortManagerFromCard = async (device) => {
|
||||
await openPortManager()
|
||||
}
|
||||
|
||||
const ntpSyncing = ref(false)
|
||||
const ntpSyncVisible = ref(false)
|
||||
const ntpPhase = ref('config') // 'config' | 'results'
|
||||
const ntpForm = ref({ old_server: '172.16.0.254', new_server: '172.16.1.252' })
|
||||
const ntpSyncResult = ref({ total: 0, success: 0, failed: 0, results: [] })
|
||||
|
||||
const openNtpSync = () => {
|
||||
ntpPhase.value = 'config'
|
||||
ntpSyncResult.value = { total: 0, success: 0, failed: 0, results: [] }
|
||||
ntpSyncVisible.value = true
|
||||
}
|
||||
|
||||
const runNtpSync = async () => {
|
||||
ntpSyncing.value = true
|
||||
try {
|
||||
const { data } = await request.post('/olt/sync-ntp', ntpForm.value)
|
||||
ntpSyncResult.value = data
|
||||
ntpPhase.value = 'results'
|
||||
} catch (error) {
|
||||
ElMessage.error(error.response?.data?.detail || 'NTP 同步失败')
|
||||
} finally {
|
||||
ntpSyncing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchRegions()
|
||||
fetchDevices()
|
||||
|
||||
Reference in New Issue
Block a user