This commit is contained in:
qsc
2026-06-06 02:37:14 +08:00
parent 129dc41208
commit 00dd907f65
10 changed files with 2328 additions and 61 deletions
+190 -15
View File
@@ -1,43 +1,141 @@
from tkinter import messagebox
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from core.Function.tracert_fun import TracertFun
from core.ui.components import Console, Page, action_bar, button, field
from core.ui.components import Console, Page, action_bar, button, combo, field
class TracertTab(Page):
def __init__(self, parent):
super().__init__(parent, "路由追踪", "查看从本机到目标地址的网络跳点和响应时间")
self.body.rowconfigure(1, weight=1)
super().__init__(parent, "路由追踪", "结构化查看跳点、延迟、超时、波动和诊断摘要")
self.body.rowconfigure(3, weight=1)
target = self.section("追踪参数", 0, columns=4)
self.target = field(target, "目标 IP / 域名", 1, 0, "8.8.8.8", 28)
self.max_hops = field(target, "最大跳数", 1, 1, "20", 12)
self.timeout_ms = field(target, "单跳超时(ms", 1, 2, "800", 14)
actions = action_bar(target, 2, 4)
self.start_btn = button(actions, "↗ 开始追踪", self.start_trace, "Primary.TButton")
self.stop_btn = button(actions, "■ 停止", self.stop_trace, "Danger.TButton")
status = self.section("实时状态", 0, columns=7)
self.state = field(status, "状态", 1, 0, "等待", 10)
self.run_state = field(status, "轮次", 1, 1, "0/0", 8)
self.current_hop = field(status, "当前跳", 1, 2, "0", 8)
self.timeout_hops = field(status, "超时跳", 1, 3, "0", 8)
self.high_hops = field(status, "高延迟", 1, 4, "0", 8)
self.max_latency = field(status, "最大延迟", 1, 5, "0.0 ms", 12)
self.elapsed = field(status, "耗时", 1, 6, "0.0s", 10)
for item in (
self.state,
self.run_state,
self.current_hop,
self.timeout_hops,
self.high_hops,
self.max_latency,
self.elapsed,
):
item["entry"].configure(state="disabled")
output = self.section("输出控制台", 1, columns=1)
params = self.section("追踪参数", 1, columns=7)
self.target = field(params, "目标 IP / 域名", 1, 0, "8.8.8.8", 26)
self.address_family = combo(params, "地址族", 1, 1, ["自动", "IPv4", "IPv6"], "自动", 10)
self.mode = combo(params, "模式", 1, 2, ["单次", "指定次数", "持续"], "单次", 12)
self.repeat_count = field(params, "次数", 1, 3, "3", 8)
self.interval_ms = field(params, "间隔 ms", 1, 4, "1000", 10)
self.max_hops = field(params, "最大跳数", 1, 5, "20", 10)
self.timeout_ms = field(params, "单跳超时 ms", 1, 6, "800", 12)
self.high_latency_ms = field(params, "高延迟阈值 ms", 2, 0, "100", 12)
self.resolve_names_var = tk.BooleanVar(value=False)
self._check(params, "解析主机名", self.resolve_names_var, 2, 1)
actions = action_bar(params, 3, 7)
self.start_btn = button(actions, "开始追踪", self.start_trace, "Primary.TButton")
self.stop_btn = button(actions, "停止", self.stop_trace, "Danger.TButton")
self.copy_btn = button(actions, "复制摘要", self.copy_summary, "Secondary.TButton")
self.export_btn = button(actions, "导出 CSV", self.export_results, "Secondary.TButton")
results = self.section("跳点结果", 2, columns=1)
results.rowconfigure(1, weight=1)
results.columnconfigure(0, weight=1)
self.results_tree = ttk.Treeview(
results,
columns=("run", "hop", "probe1", "probe2", "probe3", "avg", "jitter", "host", "ip", "status"),
show="headings",
height=9,
)
headings = {
"run": "轮次",
"hop": "跳数",
"probe1": "RTT 1",
"probe2": "RTT 2",
"probe3": "RTT 3",
"avg": "平均",
"jitter": "波动",
"host": "主机名",
"ip": "IP",
"status": "判断",
}
widths = {
"run": 60,
"hop": 60,
"probe1": 70,
"probe2": 70,
"probe3": 70,
"avg": 80,
"jitter": 80,
"host": 200,
"ip": 150,
"status": 90,
}
for column, title in headings.items():
self.results_tree.heading(column, text=title)
self.results_tree.column(column, width=widths[column], anchor="w")
self.results_tree.tag_configure("ok", foreground="#15803d")
self.results_tree.tag_configure("timeout", foreground="#b7791f")
self.results_tree.tag_configure("high_latency", foreground="#dc2626")
self.results_tree.tag_configure("jitter", foreground="#b7791f")
self.results_tree.grid(row=1, column=0, sticky="nsew", padx=18, pady=(0, 18))
result_scroll = ttk.Scrollbar(results, orient="vertical", command=self.results_tree.yview)
result_scroll.grid(row=1, column=1, sticky="ns", pady=(0, 18))
self.results_tree.configure(yscrollcommand=result_scroll.set)
output = self.section("原始输出", 3, columns=1)
output.rowconfigure(1, weight=1)
output.columnconfigure(0, weight=1)
self.console = Console(output, height=22)
self.console = Console(output, height=12)
self.console.grid(row=1, column=0, sticky="nsew", padx=18, pady=(0, 18))
self.tracert_fun = TracertFun(self.write, self.on_task_done)
self.tracert_fun = TracertFun(self.write, self.on_task_done, self.update_status, self.add_result)
def _check(self, parent, text, variable, row, column):
frame = ttk.Frame(parent, style="Panel.TFrame")
frame.grid(row=row, column=column, sticky="ew", padx=18, pady=(22, 14))
ttk.Checkbutton(frame, text=text, variable=variable).pack(anchor="w")
def write(self, text, tag=None):
self.console.write(text, tag)
def clear(self):
self.console.clear()
for item in self.results_tree.get_children():
self.results_tree.delete(item)
self.update_status(
{
"state": "等待",
"current_run": 0,
"total_runs": 0,
"current_hop": 0,
"timeout_hops": 0,
"high_latency_hops": 0,
"max_latency_ms": 0,
"elapsed": 0,
}
)
def start_trace(self):
try:
self.console.clear()
self.clear()
self.tracert_fun.start_tracert(
self.target["var"].get(),
int(self.max_hops["var"].get()),
int(self.timeout_ms["var"].get()),
self.trace_options(),
)
self.start_btn.configure(state="disabled")
except Exception as exc:
self.start_btn.configure(state="normal")
messagebox.showwarning("无法开始路由追踪", str(exc))
def stop_trace(self):
@@ -46,5 +144,82 @@ class TracertTab(Page):
except Exception as exc:
messagebox.showinfo("提示", str(exc))
def trace_options(self):
return {
"address_family": self.address_family["var"].get(),
"resolve_names": self.resolve_names_var.get(),
"mode": self.mode["var"].get(),
"repeat_count": self.repeat_count["var"].get(),
"interval_ms": self.interval_ms["var"].get(),
"high_latency_ms": self.high_latency_ms["var"].get(),
}
def export_results(self):
path = filedialog.asksaveasfilename(
title="导出路由追踪结果",
defaultextension=".csv",
filetypes=[("CSV 文件", "*.csv")],
)
if not path:
return
try:
self.tracert_fun.export_results(path)
self.write(f"已导出结果: {path}\n", "success")
except Exception as exc:
messagebox.showwarning("导出失败", str(exc))
def copy_summary(self):
text = self.tracert_fun.copy_summary()
if not text:
messagebox.showinfo("提示", "还没有可复制的诊断摘要")
return
self.clipboard_clear()
self.clipboard_append(text)
self.write("已复制诊断摘要到剪贴板\n", "success")
def update_status(self, stats):
def apply():
total_runs = stats.get("total_runs", 0)
run_text = f"{stats.get('current_run', 0)}/持续" if total_runs == 0 else f"{stats.get('current_run', 0)}/{total_runs}"
values = [
(self.state, stats.get("state", "等待")),
(self.run_state, run_text),
(self.current_hop, f"{stats.get('current_hop', 0)}/{stats.get('max_hops', 0)}"),
(self.timeout_hops, str(stats.get("timeout_hops", 0))),
(self.high_hops, str(stats.get("high_latency_hops", 0))),
(self.max_latency, f"{stats.get('max_latency_ms', 0):.1f} ms"),
(self.elapsed, f"{stats.get('elapsed', 0):.1f}s"),
]
for item, value in values:
item["entry"].configure(state="normal")
item["var"].set(value)
item["entry"].configure(state="disabled")
self.after(0, apply)
def add_result(self, row):
def apply():
avg = f"{row.get('avg_ms', 0):.1f} ms" if row.get("avg_ms") else "-"
jitter = f"{row.get('jitter_ms', 0):.1f} ms" if row.get("jitter_ms") else "-"
self.results_tree.insert(
"",
"end",
values=(
row.get("run", ""),
row.get("hop", ""),
row.get("probe1", ""),
row.get("probe2", ""),
row.get("probe3", ""),
avg,
jitter,
row.get("host", ""),
row.get("ip", ""),
row.get("status_text", ""),
),
tags=(row.get("status", ""),),
)
self.after(0, apply)
def on_task_done(self):
self.after(0, lambda: self.start_btn.configure(state="normal"))