tb-order-sync 0.4.1 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/dashboard.py CHANGED
@@ -14,10 +14,25 @@ from rich.panel import Panel
14
14
  from rich.table import Table
15
15
  from rich.text import Text
16
16
 
17
- from config.settings import Settings, get_settings
17
+ from config.settings import APP_VERSION, Settings, get_settings
18
18
  from services.daemon_service import DaemonService
19
19
  from services.state_service import StateService
20
20
 
21
+ _LOGO_LINES = [
22
+ ("████████╗██████╗ ██████╗ ██████╗ ██████╗ ███████╗██████╗", "bold #8ecae6"),
23
+ ("╚══██╔══╝██╔══██╗ ██╔═══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗", "bold #6ccff6"),
24
+ (" ██║ ██████╔╝ ██║ ██║██████╔╝██║ ██║█████╗ ██████╔╝", "bold #38bdf8"),
25
+ (" ██║ ██╔══██╗ ██║ ██║██╔══██╗██║ ██║██╔══╝ ██╔══██╗", "bold #22d3ee"),
26
+ (" ██║ ██████╔╝ ╚██████╔╝██║ ██║██████╔╝███████╗██║ ██║", "bold #2dd4bf"),
27
+ (" ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝", "bold #86efac"),
28
+ ]
29
+ _MODAL_ICONS = {
30
+ "success": ("●", "#10b981"),
31
+ "warning": ("●", "#f59e0b"),
32
+ "error": ("●", "#ef4444"),
33
+ "info": ("●", "#38bdf8"),
34
+ }
35
+
21
36
 
22
37
  class DashboardApp:
23
38
  """Interactive terminal UI for daily operations."""
@@ -57,38 +72,35 @@ class DashboardApp:
57
72
  last_run = self._state_svc.load_last_run(quiet=True)
58
73
  autostart_status = self._daemon.autostart_status()
59
74
 
60
- title = Text("多表格同步与退款标记服务", style="bold white")
61
- subtitle = Text("Scheduler Console", style="bold #8ecae6")
62
- header = Panel(
63
- Align.center(Group(title, subtitle)),
64
- border_style="#219ebc",
65
- box=box.HEAVY,
66
- padding=(1, 2),
67
- )
75
+ header = self._build_header(daemon_status, last_run)
68
76
 
69
77
  runtime_panel = Panel(
70
78
  self._build_runtime_table(),
71
79
  title="[bold #023047]运行配置[/bold #023047]",
72
80
  border_style="#8ecae6",
73
81
  box=box.ROUNDED,
82
+ padding=(1, 2),
74
83
  )
75
84
  daemon_panel = Panel(
76
85
  self._build_daemon_table(daemon_status, autostart_status),
77
86
  title="[bold #023047]守护进程[/bold #023047]",
78
87
  border_style="#90be6d" if daemon_status.running else "#f4a261",
79
88
  box=box.ROUNDED,
89
+ padding=(1, 2),
80
90
  )
81
91
  state_panel = Panel(
82
92
  self._build_state_table(state, last_run),
83
93
  title="[bold #023047]同步状态[/bold #023047]",
84
94
  border_style="#ffb703",
85
95
  box=box.ROUNDED,
96
+ padding=(1, 2),
86
97
  )
87
98
  config_panel = Panel(
88
99
  self._build_config_table(),
89
100
  title="[bold #023047]接入状态[/bold #023047]",
90
101
  border_style="#fb8500" if self._is_config_ready() else "#d62828",
91
102
  box=box.ROUNDED,
103
+ padding=(1, 2),
92
104
  )
93
105
 
94
106
  actions = Panel(
@@ -96,6 +108,7 @@ class DashboardApp:
96
108
  title="[bold #023047]操作台[/bold #023047]",
97
109
  border_style="#219ebc",
98
110
  box=box.ROUNDED,
111
+ padding=(1, 2),
99
112
  )
100
113
 
101
114
  footer = Panel(
@@ -116,6 +129,63 @@ class DashboardApp:
116
129
  footer,
117
130
  )
118
131
 
132
+ def _build_header(self, daemon_status, last_run) -> Panel:
133
+ logo = Text(justify="center")
134
+ for line, style in _LOGO_LINES:
135
+ logo.append(line, style=style)
136
+ logo.append("\n")
137
+ logo.append("Tencent Docs Order Sync Console", style="bold #e0fbfc")
138
+
139
+ hero = Group(
140
+ Align.center(logo),
141
+ Align.right(self._build_version_badge()),
142
+ )
143
+
144
+ badges = Table.grid(expand=True)
145
+ badges.add_column(justify="center")
146
+ badges.add_column(justify="center")
147
+ badges.add_column(justify="center")
148
+ badges.add_row(
149
+ self._build_badge("运行模式", f"{self._settings.gross_profit_mode.value} / {self._settings.refund_match_mode.value}", "#0ea5e9"),
150
+ self._build_badge("守护状态", "运行中" if daemon_status.running else "未运行", "#10b981" if daemon_status.running else "#f59e0b"),
151
+ self._build_badge("最近结果", self._last_run_label(last_run), "#22c55e" if last_run and last_run.success else "#ef4444" if last_run else "#64748b"),
152
+ )
153
+
154
+ subtitle = Text("多表格同步与退款标记服务", style="bold white", justify="center")
155
+ hint = Text("输入编号执行任务,所有结果与失败原因会在控制台内直接返回", style="dim", justify="center")
156
+
157
+ body = Group(
158
+ hero,
159
+ Align.center(subtitle),
160
+ Align.center(hint),
161
+ badges,
162
+ )
163
+ return Panel(
164
+ body,
165
+ border_style="#219ebc",
166
+ box=box.HEAVY,
167
+ padding=(1, 2),
168
+ )
169
+
170
+ @staticmethod
171
+ def _build_badge(label: str, value: str, color: str) -> Panel:
172
+ inner = Table.grid(padding=(0, 1))
173
+ inner.add_column(justify="center")
174
+ inner.add_row(Text(label, style="bold white"))
175
+ inner.add_row(Text(value, style="bold white"))
176
+ return Panel(inner, border_style=color, box=box.ROUNDED, padding=(0, 1))
177
+
178
+ @staticmethod
179
+ def _build_version_badge() -> Panel:
180
+ text = Text(f"v{APP_VERSION}", style="bold #0f172a", justify="center")
181
+ return Panel(text, border_style="#94d2bd", box=box.ROUNDED, padding=(0, 2), title="版本")
182
+
183
+ @staticmethod
184
+ def _last_run_label(last_run) -> str:
185
+ if last_run is None:
186
+ return "暂无记录"
187
+ return "成功" if last_run.success else "失败"
188
+
119
189
  def _build_runtime_table(self) -> Table:
120
190
  table = Table(box=None, show_header=False, pad_edge=False)
121
191
  table.add_column(style="bold white")
@@ -168,9 +238,9 @@ class DashboardApp:
168
238
  return table
169
239
 
170
240
  def _build_action_table(self) -> Table:
171
- table = Table(box=box.SIMPLE_HEAVY, expand=True)
172
- table.add_column("编号", justify="center", style="bold #219ebc", width=6)
173
- table.add_column("动作", style="bold white")
241
+ table = Table(box=box.SIMPLE_HEAVY, expand=True, row_styles=["none", "dim"])
242
+ table.add_column("编号", justify="center", style="bold #38bdf8", width=6)
243
+ table.add_column("动作", style="bold white", width=18)
174
244
  table.add_column("说明", style="#023047")
175
245
  table.add_row("1", "执行全部任务", "毛利计算 + 退款匹配")
176
246
  table.add_row("2", "模拟执行", "全部任务 dry-run,不写入表格")
@@ -228,7 +298,7 @@ class DashboardApp:
228
298
  from cli.commands import execute_tasks
229
299
 
230
300
  results = execute_tasks(self._settings, task, dry_run=dry_run)
231
- table = Table(box=box.SIMPLE_HEAVY, expand=True)
301
+ table = Table(box=box.SIMPLE_HEAVY, expand=True, row_styles=["none", "dim"])
232
302
  table.add_column("任务", style="bold cyan")
233
303
  table.add_column("结果", justify="center")
234
304
  table.add_column("读取", justify="right")
@@ -251,8 +321,19 @@ class DashboardApp:
251
321
  failure_table.add_column("失败原因", style="white")
252
322
  for item in failures:
253
323
  failure_table.add_row(item.task_name.value, item.error_message or "")
254
- body = Group(table, Panel(failure_table, title="失败详情", border_style="red", box=box.ROUNDED))
255
- self._pause_with_panel(Panel(body, title="执行结果", border_style="#219ebc", box=box.ROUNDED))
324
+ body = Group(
325
+ self._build_modal_summary("执行完成,但存在失败项", style="error"),
326
+ table,
327
+ Panel(failure_table, title="失败详情", border_style="red", box=box.ROUNDED, padding=(1, 2)),
328
+ )
329
+ self._pause_with_panel(body, title="执行结果", border_style="#ef4444")
330
+ return
331
+
332
+ body = Group(
333
+ self._build_modal_summary("执行完成,结果已落地", style="success"),
334
+ table,
335
+ )
336
+ self._pause_with_panel(body, title="执行结果", border_style="#219ebc")
256
337
 
257
338
  def _daemon_action(self, action: str) -> None:
258
339
  if action in {"start", "autostart-enable"} and not self._ensure_config():
@@ -271,14 +352,47 @@ class DashboardApp:
271
352
  else:
272
353
  status = self._daemon.autostart_status()
273
354
 
274
- self._pause_with_panel(Panel(status.message, title="守护结果", border_style="#90be6d", box=box.ROUNDED))
355
+ style = "success"
356
+ border = "#90be6d"
357
+ if "失败" in status.message or "未找到" in status.message:
358
+ style = "error"
359
+ border = "#ef4444"
360
+ elif "未启用" in status.message or "未运行" in status.message:
361
+ style = "warning"
362
+ border = "#f59e0b"
363
+
364
+ body = Group(
365
+ self._build_modal_summary(status.message, style=style),
366
+ self._build_kv_table({
367
+ "动作": action,
368
+ "目标": getattr(status, "target", "") or "-",
369
+ }),
370
+ )
371
+ self._pause_with_panel(body, title="守护结果", border_style=border)
275
372
 
276
373
  def _show_log_tail(self) -> None:
277
374
  content = self._daemon.read_log_tail(lines=40)
278
- body = content if content else "后台日志暂时为空。"
279
- self._pause_with_panel(
280
- Panel(body, title=f"后台日志 · {self._daemon.log_file.name}", border_style="#ffb703", box=box.ROUNDED)
281
- )
375
+ if content:
376
+ log_panel = Panel(
377
+ Text(content, style="#e5e7eb"),
378
+ title=f"后台日志 · {self._daemon.log_file.name}",
379
+ border_style="#ffb703",
380
+ box=box.ROUNDED,
381
+ padding=(1, 2),
382
+ )
383
+ body = Group(
384
+ self._build_modal_summary("以下为最近 40 行后台日志", style="info"),
385
+ log_panel,
386
+ )
387
+ else:
388
+ body = Group(
389
+ self._build_modal_summary("后台日志暂时为空", style="warning"),
390
+ self._build_kv_table({
391
+ "日志文件": self._daemon.log_file.name,
392
+ "日志目录": str(self._daemon.log_file.parent),
393
+ }),
394
+ )
395
+ self._pause_with_panel(body, title="后台日志", border_style="#ffb703")
282
396
 
283
397
  def _run_setup(self, *, check: bool) -> None:
284
398
  from cli.setup import cmd_setup
@@ -300,14 +414,14 @@ class DashboardApp:
300
414
  def _ensure_config(self) -> bool:
301
415
  if self._is_config_ready():
302
416
  return True
303
- self._pause_with_panel(
304
- Panel(
305
- "腾讯文档必填配置尚未完成,请先运行配置向导。",
306
- title="配置未完成",
307
- border_style="red",
308
- box=box.ROUNDED,
309
- )
417
+ body = Group(
418
+ self._build_modal_summary("腾讯文档必填配置尚未完成", style="error"),
419
+ self._build_kv_table({
420
+ "建议动作": "先运行 tb setup",
421
+ "后续检查": "配置完成后运行 tb check",
422
+ }),
310
423
  )
424
+ self._pause_with_panel(body, title="配置未完成", border_style="#ef4444")
311
425
  return False
312
426
 
313
427
  def _is_config_ready(self) -> bool:
@@ -322,11 +436,35 @@ class DashboardApp:
322
436
  ]
323
437
  return all(bool(value.strip()) for value in fields)
324
438
 
325
- def _pause_with_panel(self, panel: Panel) -> None:
439
+ def _pause_with_panel(self, body, *, title: str, border_style: str) -> None:
326
440
  self.console.clear()
327
- self.console.print(panel)
441
+ self.console.print(
442
+ Panel(
443
+ body,
444
+ title=title,
445
+ border_style=border_style,
446
+ box=box.ROUNDED,
447
+ padding=(1, 2),
448
+ )
449
+ )
328
450
  self._wait()
329
451
 
452
+ def _build_modal_summary(self, message: str, *, style: str) -> Panel:
453
+ icon, color = _MODAL_ICONS[style]
454
+ text = Text(justify="center")
455
+ text.append(f"{icon} ", style=f"bold {color}")
456
+ text.append(message, style="bold white")
457
+ return Panel(text, border_style=color, box=box.ROUNDED, padding=(0, 1))
458
+
459
+ @staticmethod
460
+ def _build_kv_table(rows: dict[str, str]) -> Table:
461
+ table = Table(box=box.SIMPLE_HEAVY, expand=True, row_styles=["none", "dim"])
462
+ table.add_column("项目", style="bold cyan", width=18)
463
+ table.add_column("内容", style="white")
464
+ for key, value in rows.items():
465
+ table.add_row(key, value)
466
+ return table
467
+
330
468
  def _wait(self) -> None:
331
469
  self.console.input("[dim]按回车返回控制台[/dim]")
332
470