fix: 恢复Agent指标并修复时间弹层
- 使用按主键更新避免SQLite字段错误 - 自定义时间选择器改用Portal避免卡片遮挡
This commit is contained in:
1 parent
af98940211
commit
d9106dde6e
2 files changed
+32
-58
No files matched your search
+13
-30
@@ -127,51 +127,34 @@ func (h *AgentHandler) ReceiveReport(c *gin.Context) {
|
|||||||
memMB := int64(memGB * 1024) // GB → MB
|
memMB := int64(memGB * 1024) // GB → MB
|
||||||
diskGB := int64(totalDiskGB + 0.5) // 39.99 → 40
|
diskGB := int64(totalDiskGB + 0.5) // 39.99 → 40
|
||||||
|
|
||||||
// 直接 SQL 更新 asset_servers 监控字段
|
// 使用 map 按主键更新,避免可选系统字段导致原始 SQL 占位参数错位。 // Logan.Li
|
||||||
// 若 AgentVersion 非空则同步写入,否则保留数据库中已有版本号(防止旧版Agent空字段覆盖已设置的版本)
|
updates := map[string]interface{}{
|
||||||
updateSQL := `UPDATE asset_servers SET
|
"cpu_usage": req.CPU.UsagePercent, "memory_usage": req.Memory.UsagePercent,
|
||||||
cpu_usage = ?, cpu_iowait = ?, memory_usage = ?, disk_usage = ?,
|
"disk_usage": diskAvg,
|
||||||
cpu = ?, memory = ?, disk = ?,
|
"cpu": req.CPU.CoreCount, "memory": memMB, "disk": diskGB,
|
||||||
status = ?, agent_status = ?` + func() string {
|
"status": "running", "agent_status": "online", "updated_at": time.Now(),
|
||||||
if req.AgentVersion != "" {
|
|
||||||
return `, agent_version = ?`
|
|
||||||
}
|
|
||||||
return ``
|
|
||||||
}()
|
|
||||||
args := []interface{}{
|
|
||||||
req.CPU.UsagePercent, req.CPU.IOWait, req.Memory.UsagePercent, diskAvg,
|
|
||||||
req.CPU.CoreCount, memMB, diskGB,
|
|
||||||
"running", "online",
|
|
||||||
}
|
}
|
||||||
if req.AgentVersion != "" {
|
if req.AgentVersion != "" {
|
||||||
args = append(args, req.AgentVersion)
|
updates["agent_version"] = req.AgentVersion
|
||||||
}
|
}
|
||||||
if req.System != nil {
|
if req.System != nil {
|
||||||
if req.System.OSVersion != "" {
|
if req.System.OSVersion != "" {
|
||||||
updateSQL += ", os = ?"
|
updates["os"] = req.System.OSVersion
|
||||||
args = append(args, req.System.OSVersion)
|
|
||||||
}
|
}
|
||||||
if req.System.Kernel != "" {
|
if req.System.Kernel != "" {
|
||||||
updateSQL += ", kernel = ?"
|
updates["kernel"] = req.System.Kernel
|
||||||
args = append(args, req.System.Kernel)
|
|
||||||
}
|
}
|
||||||
if req.System.Uptime > 0 {
|
if req.System.Uptime > 0 {
|
||||||
updateSQL += ", uptime = ?"
|
updates["uptime"] = req.System.Uptime
|
||||||
args = append(args, req.System.Uptime)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateSQL += ", updated_at = ?"
|
var result *gorm.DB
|
||||||
args = append(args, time.Now())
|
|
||||||
if assetMatched {
|
if assetMatched {
|
||||||
updateSQL += " WHERE id = ?"
|
result = h.db.Model(&model.Server{}).Where("id = ?", matchedServer.ID).Updates(updates)
|
||||||
args = append(args, matchedServer.ID)
|
|
||||||
} else {
|
} else {
|
||||||
updateSQL += " WHERE hostname = ? OR ip = ?"
|
result = h.db.Model(&model.Server{}).Where("hostname = ? OR ip = ?", req.Hostname, matchIP).Updates(updates)
|
||||||
args = append(args, req.Hostname, matchIP)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result := h.db.Exec(updateSQL, args...)
|
|
||||||
|
|
||||||
// 如果还是没匹配到 —— 按 hostname 显式查重后再注册 // Logan.Li
|
// 如果还是没匹配到 —— 按 hostname 显式查重后再注册 // Logan.Li
|
||||||
// asset_servers.hostname 无 UNIQUE 索引,OnConflict 不可靠,故先 First 查重,
|
// asset_servers.hostname 无 UNIQUE 索引,OnConflict 不可靠,故先 First 查重,
|
||||||
// 避免 Agent 每次上报(10s)都新建记录导致资产越来越多。
|
// 避免 Agent 每次上报(10s)都新建记录导致资产越来越多。
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// 主机监控时间范围选择器 — 预设 + 自定义 // Logan.Li
|
// 主机监控时间范围选择器 — 预设 + 自定义 // Logan.Li
|
||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState } from 'react';
|
||||||
import { Calendar, ChevronDown, Timer } from 'lucide-react';
|
import { Calendar, ChevronDown, Timer } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
import {
|
import {
|
||||||
TIME_RANGE_PRESETS,
|
TIME_RANGE_PRESETS,
|
||||||
type HostMonitorTimeRange,
|
type HostMonitorTimeRange,
|
||||||
@@ -21,15 +22,6 @@ export function HostTimeRangePicker({ value, onChange }: Props) {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
const [customStart, setCustomStart] = useState(toDatetimeLocalValue(new Date(now.getTime() - 3600_000)));
|
const [customStart, setCustomStart] = useState(toDatetimeLocalValue(new Date(now.getTime() - 3600_000)));
|
||||||
const [customEnd, setCustomEnd] = useState(toDatetimeLocalValue(now));
|
const [customEnd, setCustomEnd] = useState(toDatetimeLocalValue(now));
|
||||||
const popRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handler = (e: MouseEvent) => {
|
|
||||||
if (popRef.current && !popRef.current.contains(e.target as Node)) setCustomOpen(false);
|
|
||||||
};
|
|
||||||
document.addEventListener('mousedown', handler);
|
|
||||||
return () => document.removeEventListener('mousedown', handler);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const selectPreset = (id: TimeRangePresetId) => {
|
const selectPreset = (id: TimeRangePresetId) => {
|
||||||
if (id === 'custom') return;
|
if (id === 'custom') return;
|
||||||
@@ -72,20 +64,20 @@ export function HostTimeRangePicker({ value, onChange }: Props) {
|
|||||||
{p.label}
|
{p.label}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
<div className="relative" ref={popRef}>
|
<Popover open={customOpen} onOpenChange={setCustomOpen}>
|
||||||
<Button
|
<PopoverTrigger asChild>
|
||||||
type="button"
|
<Button
|
||||||
variant="ghost"
|
type="button"
|
||||||
size="sm"
|
variant="ghost"
|
||||||
className={`h-6 text-[11px] px-2 rounded gap-1 ${value.preset === 'custom' ? 'dg-btn-primary shadow-sm' : 'dg-text-secondary hover:dg-text-primary'}`}
|
size="sm"
|
||||||
onClick={() => setCustomOpen((v) => !v)}
|
className={`h-6 text-[11px] px-2 rounded gap-1 ${value.preset === 'custom' ? 'dg-btn-primary shadow-sm' : 'dg-text-secondary hover:dg-text-primary'}`}
|
||||||
>
|
>
|
||||||
<Calendar className="h-3 w-3" />
|
<Calendar className="h-3 w-3" />
|
||||||
自定义
|
自定义
|
||||||
<ChevronDown className={`h-3 w-3 transition-transform ${customOpen ? 'rotate-180' : ''}`} />
|
<ChevronDown className={`h-3 w-3 transition-transform ${customOpen ? 'rotate-180' : ''}`} />
|
||||||
</Button>
|
</Button>
|
||||||
{customOpen && (
|
</PopoverTrigger>
|
||||||
<div className="absolute right-0 top-full mt-1 z-50 w-72 rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-primary)] p-3 shadow-lg">
|
<PopoverContent align="end" sideOffset={4} className="z-[100] w-72 rounded-lg border-[var(--dg-border)] bg-[var(--dg-bg-primary)] p-3 shadow-lg">
|
||||||
<p className="text-xs font-medium text-[var(--dg-text-primary)] mb-2"> {i18n.t('monitor.hosttimerangepi.s0122e022')} </p>
|
<p className="text-xs font-medium text-[var(--dg-text-primary)] mb-2"> {i18n.t('monitor.hosttimerangepi.s0122e022')} </p>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div>
|
<div>
|
||||||
@@ -115,13 +107,12 @@ export function HostTimeRangePicker({ value, onChange }: Props) {
|
|||||||
应用
|
应用
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PopoverContent>
|
||||||
)}
|
</Popover>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10px] dg-text-muted tabular-nums hidden md:inline">
|
<span className="text-[10px] dg-text-muted tabular-nums hidden md:inline">
|
||||||
{formatRangeSummary(value)}
|
{formatRangeSummary(value)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user