OK
This commit is contained in:
+190
-15
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user