Python-based time management application with UDP discovery, TCP protocol communication, time sync, and drift monitoring. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
125 lines
4.3 KiB
Python
125 lines
4.3 KiB
Python
"""탭2: PC시간 동기화 패널"""
|
|
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
import threading
|
|
from datetime import datetime
|
|
|
|
from models.controller import Controller
|
|
from services.time_sync_service import sync_all
|
|
|
|
|
|
class SyncPanel(ttk.Frame):
|
|
"""PC 시간 동기화 패널."""
|
|
|
|
def __init__(self, parent, shared_controllers: list[Controller]):
|
|
super().__init__(parent)
|
|
self._controllers = shared_controllers
|
|
self._check_vars: list[tk.BooleanVar] = []
|
|
self._syncing = False
|
|
self._build_ui()
|
|
self._update_clock()
|
|
|
|
def _build_ui(self):
|
|
# --- 상단: 현재 PC 시간 ---
|
|
top = ttk.LabelFrame(self, text="현재 PC 시간", padding=10)
|
|
top.pack(fill=tk.X, padx=5, pady=5)
|
|
|
|
self._clock_label = ttk.Label(top, text="", font=("Consolas", 18))
|
|
self._clock_label.pack()
|
|
|
|
# --- 중간: 컨트롤러 체크리스트 ---
|
|
mid = ttk.LabelFrame(self, text="동기화 대상 컨트롤러", padding=5)
|
|
mid.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
|
|
|
btn_row = ttk.Frame(mid)
|
|
btn_row.pack(fill=tk.X)
|
|
ttk.Button(btn_row, text="전체 선택", command=self._select_all).pack(side=tk.LEFT, padx=2)
|
|
ttk.Button(btn_row, text="전체 해제", command=self._deselect_all).pack(side=tk.LEFT, padx=2)
|
|
ttk.Button(btn_row, text="목록 새로고침", command=self.refresh_list).pack(side=tk.LEFT, padx=2)
|
|
|
|
self._check_frame = ttk.Frame(mid)
|
|
self._check_frame.pack(fill=tk.BOTH, expand=True, pady=5)
|
|
|
|
# --- 하단: 동기화 실행 + 결과 ---
|
|
bot = ttk.LabelFrame(self, text="동기화", padding=5)
|
|
bot.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
|
|
|
self._btn_sync = ttk.Button(bot, text="동기화 실행", command=self._on_sync)
|
|
self._btn_sync.pack(pady=5)
|
|
|
|
self._log = tk.Text(bot, height=8, state=tk.DISABLED, wrap=tk.WORD)
|
|
self._log.pack(fill=tk.BOTH, expand=True)
|
|
|
|
def _update_clock(self):
|
|
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
self._clock_label.config(text=now)
|
|
self.after(1000, self._update_clock)
|
|
|
|
def refresh_list(self):
|
|
"""컨트롤러 체크리스트 갱신."""
|
|
for w in self._check_frame.winfo_children():
|
|
w.destroy()
|
|
self._check_vars.clear()
|
|
|
|
for ctrl in self._controllers:
|
|
var = tk.BooleanVar(value=ctrl.selected)
|
|
self._check_vars.append(var)
|
|
cb = ttk.Checkbutton(
|
|
self._check_frame,
|
|
text=ctrl.display_label,
|
|
variable=var,
|
|
command=lambda c=ctrl, v=var: setattr(c, "selected", v.get()),
|
|
)
|
|
cb.pack(anchor=tk.W)
|
|
|
|
def _select_all(self):
|
|
for var in self._check_vars:
|
|
var.set(True)
|
|
for ctrl in self._controllers:
|
|
ctrl.selected = True
|
|
|
|
def _deselect_all(self):
|
|
for var in self._check_vars:
|
|
var.set(False)
|
|
for ctrl in self._controllers:
|
|
ctrl.selected = False
|
|
|
|
def _log_message(self, msg: str):
|
|
self._log.config(state=tk.NORMAL)
|
|
self._log.insert(tk.END, msg + "\n")
|
|
self._log.see(tk.END)
|
|
self._log.config(state=tk.DISABLED)
|
|
|
|
def _on_sync(self):
|
|
if self._syncing:
|
|
return
|
|
selected = [c for c in self._controllers if c.selected]
|
|
if not selected:
|
|
self._log_message("동기화할 컨트롤러가 없습니다.")
|
|
return
|
|
|
|
self._syncing = True
|
|
self._btn_sync.config(state=tk.DISABLED)
|
|
self._log_message(f"--- 동기화 시작: {datetime.now().strftime('%H:%M:%S')} ---")
|
|
|
|
threading.Thread(target=self._sync_thread, daemon=True).start()
|
|
|
|
def _sync_thread(self):
|
|
results = sync_all(self._controllers)
|
|
self.after(0, self._sync_done, results)
|
|
|
|
def _sync_done(self, results):
|
|
self._syncing = False
|
|
self._btn_sync.config(state=tk.NORMAL)
|
|
|
|
ok_count = 0
|
|
for ctrl, ok, msg in results:
|
|
status = "OK" if ok else "FAIL"
|
|
self._log_message(f" [{status}] {ctrl.display_label}: {msg}")
|
|
if ok:
|
|
ok_count += 1
|
|
|
|
total = len(results)
|
|
self._log_message(f"--- 완료: {ok_count}/{total} 성공 ---\n")
|