celestialflow 3.0.9__tar.gz → 3.1.0__tar.gz

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.
Files changed (41) hide show
  1. {celestialflow-3.0.9 → celestialflow-3.1.0}/PKG-INFO +14 -13
  2. {celestialflow-3.0.9 → celestialflow-3.1.0}/README.md +13 -12
  3. {celestialflow-3.0.9 → celestialflow-3.1.0}/pyproject.toml +1 -1
  4. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/__init__.py +2 -2
  5. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/css/base.css +34 -0
  6. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/css/dashboard.css +34 -70
  7. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/main.js +17 -9
  8. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/task_errors.js +1 -1
  9. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/task_statuses.js +3 -24
  10. celestialflow-3.1.0/src/celestialflow/static/js/task_summary.js +36 -0
  11. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/utils.js +34 -1
  12. celestialflow-3.0.9/src/celestialflow/task_manage.py → celestialflow-3.1.0/src/celestialflow/task_executor.py +108 -88
  13. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_graph.py +95 -115
  14. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_logging.py +32 -8
  15. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_nodes.py +4 -7
  16. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_progress.py +16 -16
  17. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_queue.py +56 -84
  18. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_report.py +103 -19
  19. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_stage.py +54 -9
  20. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_tools.py +166 -34
  21. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_types.py +40 -16
  22. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_web.py +68 -12
  23. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/templates/index.html +17 -8
  24. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow.egg-info/PKG-INFO +14 -13
  25. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow.egg-info/SOURCES.txt +3 -2
  26. celestialflow-3.0.9/tests/test_manage.py → celestialflow-3.1.0/tests/test_executor.py +24 -22
  27. {celestialflow-3.0.9 → celestialflow-3.1.0}/tests/test_nodes.py +4 -1
  28. {celestialflow-3.0.9 → celestialflow-3.1.0}/setup.cfg +0 -0
  29. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/css/errors.css +0 -0
  30. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/css/inject.css +0 -0
  31. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/favicon.ico +0 -0
  32. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/task_injection.js +0 -0
  33. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/task_structure.js +0 -0
  34. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/task_topology.js +0 -0
  35. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_structure.py +0 -0
  36. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow.egg-info/dependency_links.txt +0 -0
  37. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow.egg-info/entry_points.txt +0 -0
  38. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow.egg-info/requires.txt +0 -0
  39. {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow.egg-info/top_level.txt +0 -0
  40. {celestialflow-3.0.9 → celestialflow-3.1.0}/tests/test_graph.py +0 -0
  41. {celestialflow-3.0.9 → celestialflow-3.1.0}/tests/test_structure.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: celestialflow
3
- Version: 3.0.9
3
+ Version: 3.1.0
4
4
  Summary: A flexible GRAPH-based task orchestration framework.
5
5
  Author-email: Mr-xiaotian <mingxiaomingtian@gmail.com>
6
6
  License: MIT
@@ -49,16 +49,16 @@ Requires-Dist: celestialtree
49
49
  - 相比 Airflow/Dagster 更轻、更快开始
50
50
  - 相比 multiprocessing/threading 更结构化,可直接表达 loop / complete graph 等复杂依赖模式
51
51
 
52
- 框架的基本单元为 **TaskManager**,可独立运行,并支持四种执行模式:
52
+ 框架的基本单元为 **TaskExecutor**,可独立运行,并支持四种执行模式:
53
53
 
54
54
  * **线性(serial)**
55
55
  * **多线程(thread)**
56
56
  * **多进程(process)**
57
57
  * **协程(async)**
58
58
 
59
- TaskManager 实现了对任务的结果缓存,任务去重,进度条显示,多执行模式比较等功能,单独使用也很好用。
59
+ TaskExecutor 实现了对任务的结果缓存,任务去重,进度条显示,多执行模式比较等功能,单独使用也很好用。
60
60
 
61
- 但除去直接使用 TaskManager,更重要的是使用其子类**TaskStage**。TaskStage 可以互相连接,形成具有上游与下游依赖关系的任务图(**TaskGraph**)。下游 stage 会自动接收上游执行完成的结果作为输入,从而形成明确的数据流。
61
+ 但除去直接使用 TaskExecutor,更重要的是使用其子类**TaskStage**。TaskStage 可以互相连接,形成具有上游与下游依赖关系的任务图(**TaskGraph**)。下游 stage 会自动接收上游执行完成的结果作为输入,从而形成明确的数据流。
62
62
 
63
63
  TaskStage 的任务执行模式只有两种:
64
64
 
@@ -136,7 +136,7 @@ pip install celestialflow
136
136
  一个简单的可运行代码:
137
137
 
138
138
  ```python
139
- from celestialflow import TaskManager, TaskGraph
139
+ from celestialflow import TaskStage, TaskGraph
140
140
 
141
141
  def add(x, y):
142
142
  return x + y
@@ -146,8 +146,8 @@ def square(x):
146
146
 
147
147
  if __name__ == "__main__":
148
148
  # 定义两个任务节点
149
- stage1 = TaskManager(add, execution_mode="thread", unpack_task_args=True)
150
- stage2 = TaskManager(square, execution_mode="thread")
149
+ stage1 = TaskStage(add, execution_mode="thread", unpack_task_args=True)
150
+ stage2 = TaskStage(square, execution_mode="thread")
151
151
 
152
152
  # 构建任务图结构
153
153
  stage1.set_graph_context([stage2], stage_mode="process", stage_name="Adder")
@@ -172,8 +172,9 @@ if __name__ == "__main__":
172
172
 
173
173
  若你想了解框架的整体结构与核心组件,下面的参考文档会对你有帮助:
174
174
 
175
- - [🔧TaskManage/TaskStage概念](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/reference/task_manage.md)
176
- - [🌐TaskGrapg概念](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/reference/task_graph.md)
175
+ - [🔧TaskExecutor概念](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/reference/task_executor.md)
176
+ - [🔧TaskStage概念](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/reference/task_stage.md)
177
+ - [🌐TaskGraph概念](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/reference/task_graph.md)
177
178
  - [📚Go Worker概念](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/reference/go_worker.md)
178
179
 
179
180
  推荐阅读顺序:
@@ -182,7 +183,7 @@ if __name__ == "__main__":
182
183
  flowchart TD
183
184
  classDef whiteNode fill:#ffffff,stroke:#000000,color:#000000;
184
185
 
185
- TM[TaskManage.md] --> TS[TaskStage.md] --> TG[TaskGraph.md]
186
+ TM[TaskExecutor.md] --> TS[TaskStage.md] --> TG[TaskGraph.md]
186
187
  TM --> TP[TaskProgress.md]
187
188
 
188
189
  TG --> TQ[TaskQueue.md]
@@ -342,11 +343,11 @@ flowchart TD
342
343
  - 3.0.4: 新增一个抽象结构TaskQueue, 用于表示节点的所有"入边"与"出边"; 恢复未消费任务的保存功能
343
344
  - 3.0.5: 删除原有的TaskRedisTransfer节点, 并增添三种新的redis交互节点TaskRedisSink TaskRedisSource TaskRedisAck, 用于跨语言 跨进程 跨设备处理任务; 并在Web页面添加展示拓扑信息的卡片
344
345
  - 3.0.6: 添加对[CelestialTree](https://github.com/Mr-xiaotian/CelestialTree)系统的支持, 现在可以追踪单个任务的流向
345
- - 3.0.7: 将TaskStage从TaskManager中单独抽出来作为一个子类; 增加新节点TaskRouter, 可以将传入的任务选择的传给不同的下游节点, 而不是进行广播
346
+ - 3.0.7: 将TaskStage从TaskExecutor中单独抽出来作为一个子类; 增加新节点TaskRouter, 可以将传入的任务选择的传给不同的下游节点, 而不是进行广播
346
347
  - 3.0.8: 在ctree逻辑上将"任务重试"事件后的"任务成功/失败/重试"事件视为因果关系, 而非之前的并行关系; 重构错误搜集部分逻辑; 修复大量3.0.6与3.07版本引入的bug; 优化部分log表现
347
- - 3.0.9: 1/23/2026
348
+ - 3.0.9:
348
349
  - 更新前端mermaid显示中部分节点图标;
349
- - 对ctree_client的大量修改;
350
+ - 对ctree_client进行匹配CelestialTree的大量修改;
350
351
  - 将ctree_client移出为单独的project;
351
352
  - 在前端中添加error_id的显示, 为之后显示provenance_tree做准备;
352
353
  - 增加大量warning与error, 用于提醒不规范设置;
@@ -23,16 +23,16 @@
23
23
  - 相比 Airflow/Dagster 更轻、更快开始
24
24
  - 相比 multiprocessing/threading 更结构化,可直接表达 loop / complete graph 等复杂依赖模式
25
25
 
26
- 框架的基本单元为 **TaskManager**,可独立运行,并支持四种执行模式:
26
+ 框架的基本单元为 **TaskExecutor**,可独立运行,并支持四种执行模式:
27
27
 
28
28
  * **线性(serial)**
29
29
  * **多线程(thread)**
30
30
  * **多进程(process)**
31
31
  * **协程(async)**
32
32
 
33
- TaskManager 实现了对任务的结果缓存,任务去重,进度条显示,多执行模式比较等功能,单独使用也很好用。
33
+ TaskExecutor 实现了对任务的结果缓存,任务去重,进度条显示,多执行模式比较等功能,单独使用也很好用。
34
34
 
35
- 但除去直接使用 TaskManager,更重要的是使用其子类**TaskStage**。TaskStage 可以互相连接,形成具有上游与下游依赖关系的任务图(**TaskGraph**)。下游 stage 会自动接收上游执行完成的结果作为输入,从而形成明确的数据流。
35
+ 但除去直接使用 TaskExecutor,更重要的是使用其子类**TaskStage**。TaskStage 可以互相连接,形成具有上游与下游依赖关系的任务图(**TaskGraph**)。下游 stage 会自动接收上游执行完成的结果作为输入,从而形成明确的数据流。
36
36
 
37
37
  TaskStage 的任务执行模式只有两种:
38
38
 
@@ -110,7 +110,7 @@ pip install celestialflow
110
110
  一个简单的可运行代码:
111
111
 
112
112
  ```python
113
- from celestialflow import TaskManager, TaskGraph
113
+ from celestialflow import TaskStage, TaskGraph
114
114
 
115
115
  def add(x, y):
116
116
  return x + y
@@ -120,8 +120,8 @@ def square(x):
120
120
 
121
121
  if __name__ == "__main__":
122
122
  # 定义两个任务节点
123
- stage1 = TaskManager(add, execution_mode="thread", unpack_task_args=True)
124
- stage2 = TaskManager(square, execution_mode="thread")
123
+ stage1 = TaskStage(add, execution_mode="thread", unpack_task_args=True)
124
+ stage2 = TaskStage(square, execution_mode="thread")
125
125
 
126
126
  # 构建任务图结构
127
127
  stage1.set_graph_context([stage2], stage_mode="process", stage_name="Adder")
@@ -146,8 +146,9 @@ if __name__ == "__main__":
146
146
 
147
147
  若你想了解框架的整体结构与核心组件,下面的参考文档会对你有帮助:
148
148
 
149
- - [🔧TaskManage/TaskStage概念](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/reference/task_manage.md)
150
- - [🌐TaskGrapg概念](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/reference/task_graph.md)
149
+ - [🔧TaskExecutor概念](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/reference/task_executor.md)
150
+ - [🔧TaskStage概念](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/reference/task_stage.md)
151
+ - [🌐TaskGraph概念](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/reference/task_graph.md)
151
152
  - [📚Go Worker概念](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/reference/go_worker.md)
152
153
 
153
154
  推荐阅读顺序:
@@ -156,7 +157,7 @@ if __name__ == "__main__":
156
157
  flowchart TD
157
158
  classDef whiteNode fill:#ffffff,stroke:#000000,color:#000000;
158
159
 
159
- TM[TaskManage.md] --> TS[TaskStage.md] --> TG[TaskGraph.md]
160
+ TM[TaskExecutor.md] --> TS[TaskStage.md] --> TG[TaskGraph.md]
160
161
  TM --> TP[TaskProgress.md]
161
162
 
162
163
  TG --> TQ[TaskQueue.md]
@@ -316,11 +317,11 @@ flowchart TD
316
317
  - 3.0.4: 新增一个抽象结构TaskQueue, 用于表示节点的所有"入边"与"出边"; 恢复未消费任务的保存功能
317
318
  - 3.0.5: 删除原有的TaskRedisTransfer节点, 并增添三种新的redis交互节点TaskRedisSink TaskRedisSource TaskRedisAck, 用于跨语言 跨进程 跨设备处理任务; 并在Web页面添加展示拓扑信息的卡片
318
319
  - 3.0.6: 添加对[CelestialTree](https://github.com/Mr-xiaotian/CelestialTree)系统的支持, 现在可以追踪单个任务的流向
319
- - 3.0.7: 将TaskStage从TaskManager中单独抽出来作为一个子类; 增加新节点TaskRouter, 可以将传入的任务选择的传给不同的下游节点, 而不是进行广播
320
+ - 3.0.7: 将TaskStage从TaskExecutor中单独抽出来作为一个子类; 增加新节点TaskRouter, 可以将传入的任务选择的传给不同的下游节点, 而不是进行广播
320
321
  - 3.0.8: 在ctree逻辑上将"任务重试"事件后的"任务成功/失败/重试"事件视为因果关系, 而非之前的并行关系; 重构错误搜集部分逻辑; 修复大量3.0.6与3.07版本引入的bug; 优化部分log表现
321
- - 3.0.9: 1/23/2026
322
+ - 3.0.9:
322
323
  - 更新前端mermaid显示中部分节点图标;
323
- - 对ctree_client的大量修改;
324
+ - 对ctree_client进行匹配CelestialTree的大量修改;
324
325
  - 将ctree_client移出为单独的project;
325
326
  - 在前端中添加error_id的显示, 为之后显示provenance_tree做准备;
326
327
  - 增加大量warning与error, 用于提醒不规范设置;
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "celestialflow"
7
- version = "3.0.9"
7
+ version = "3.1.0"
8
8
  description = "A flexible GRAPH-based task orchestration framework."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1,5 +1,5 @@
1
1
  from .task_graph import TaskGraph
2
- from .task_manage import TaskManager
2
+ from .task_executor import TaskExecutor
3
3
  from .task_stage import TaskStage
4
4
  from .task_nodes import (
5
5
  TaskSplitter,
@@ -34,7 +34,7 @@ __all__ = [
34
34
  "TaskComplete",
35
35
  "TaskWheel",
36
36
  "TaskGrid",
37
- "TaskManager",
37
+ "TaskExecutor",
38
38
  "TaskStage",
39
39
  "TaskSplitter",
40
40
  "TaskRedisSink",
@@ -10,6 +10,40 @@
10
10
  --gray-medium: #9ca3af;
11
11
  --gray-dark: #4b5563;
12
12
  --gray-slate: #6b7280;
13
+
14
+ /* ================================
15
+ Summary palette (light mode)
16
+ ================================ */
17
+ --summary-blue-bg: #dbeafe;
18
+ --summary-orange-bg: #fed7aa;
19
+ --summary-yellow-bg: #fef3c7;
20
+ --summary-red-bg: #fee2e2;
21
+ --summary-green-bg: #d1fae5;
22
+ --summary-purple-bg: #e9d5ff;
23
+
24
+ --summary-blue-value: #2563eb;
25
+ --summary-orange-value: #ea580c;
26
+ --summary-yellow-value: #d97706;
27
+ --summary-red-value: #dc2626;
28
+ --summary-green-value: #059669;
29
+ --summary-purple-value: #7c3aed;
30
+
31
+ /* ================================
32
+ Summary palette (dark mode)
33
+ ================================ */
34
+ --summary-blue-bg-dark: #1e3a8a;
35
+ --summary-orange-bg-dark: #7c2d12;
36
+ --summary-yellow-bg-dark: #78350f;
37
+ --summary-red-bg-dark: #7f1d1d;
38
+ --summary-green-bg-dark: #14532d;
39
+ --summary-purple-bg-dark: #4c1d95;
40
+
41
+ --summary-blue-value-dark: #60a5fa;
42
+ --summary-orange-value-dark: #fdba74;
43
+ --summary-yellow-value-dark: #facc15;
44
+ --summary-red-value-dark: #f87171;
45
+ --summary-green-value-dark: #34d399;
46
+ --summary-purple-value-dark: #c4b5fd;
13
47
  }
14
48
 
15
49
  /* ================================
@@ -18,7 +18,6 @@
18
18
  margin-left: calc(-50vw + 50%); /* 左侧“负 margin”抵消 container 居中限制 */
19
19
  margin-right: calc(-50vw + 50%);
20
20
  justify-content: space-evenly;
21
- gap: 1rem;
22
21
  }
23
22
 
24
23
  .left-panel {
@@ -174,8 +173,12 @@
174
173
  font-family: monospace;
175
174
  }
176
175
 
177
- .time-estimate .elapsed { color: var(--success-color); }
178
- .time-estimate .remaining { color: var(--warning-color); }
176
+ .time-estimate .elapsed {
177
+ color: var(--success-color);
178
+ }
179
+ .time-estimate .remaining {
180
+ color: var(--warning-color);
181
+ }
179
182
 
180
183
  /* ================================
181
184
  节点运行状态: 节点运行状态样式
@@ -235,44 +238,27 @@
235
238
  text-align: center;
236
239
  padding: 1rem;
237
240
  border-radius: 0.5rem;
238
- }
239
-
240
- .summary-item.blue {
241
- background-color: #dbeafe;
242
- }
243
-
244
- .summary-item.yellow {
245
- background-color: #fef3c7;
246
- }
247
-
248
- .summary-item.red {
249
- background-color: #fee2e2;
250
- }
251
241
 
252
- .summary-item.green {
253
- background-color: #d1fae5;
242
+ &.blue { background-color: var(--summary-blue-bg); .dark-theme & { background-color: var(--summary-blue-bg-dark); } }
243
+ &.orange { background-color: var(--summary-orange-bg); .dark-theme & { background-color: var(--summary-orange-bg-dark); } }
244
+ &.yellow { background-color: var(--summary-yellow-bg); .dark-theme & { background-color: var(--summary-yellow-bg-dark); } }
245
+ &.red { background-color: var(--summary-red-bg); .dark-theme & { background-color: var(--summary-red-bg-dark); } }
246
+ &.green { background-color: var(--summary-green-bg); .dark-theme & { background-color: var(--summary-green-bg-dark); } }
247
+ &.purple { background-color: var(--summary-purple-bg); .dark-theme & { background-color: var(--summary-purple-bg-dark); } }
254
248
  }
255
249
 
256
250
  .summary-value {
257
251
  font-size: 1.875rem;
258
252
  font-weight: 700;
259
- }
260
-
261
- .summary-value.blue {
262
- color: #2563eb;
263
- }
264
253
 
265
- .summary-value.yellow {
266
- color: #d97706;
254
+ &.blue { color: var(--summary-blue-value); .dark-theme & { color: var(--summary-blue-value-dark); } }
255
+ &.orange { color: var(--summary-orange-value); .dark-theme & { color: var(--summary-orange-value-dark); } }
256
+ &.yellow { color: var(--summary-yellow-value); .dark-theme & { color: var(--summary-yellow-value-dark); } }
257
+ &.red { color: var(--summary-red-value); .dark-theme & { color: var(--summary-red-value-dark); } }
258
+ &.green { color: var(--summary-green-value); .dark-theme & { color: var(--summary-green-value-dark); } }
259
+ &.purple { color: var(--summary-purple-value); .dark-theme & { color: var(--summary-purple-value-dark); } }
267
260
  }
268
261
 
269
- .summary-value.red {
270
- color: #dc2626;
271
- }
272
-
273
- .summary-value.green {
274
- color: #059669;
275
- }
276
262
 
277
263
  .summary-label {
278
264
  font-size: 0.875rem;
@@ -289,6 +275,7 @@
289
275
  width: 100%;
290
276
  margin-left: 0;
291
277
  margin-right: 0;
278
+ gap: 2rem;
292
279
  }
293
280
  .left-panel,
294
281
  .middle-panel,
@@ -318,7 +305,7 @@
318
305
  }
319
306
 
320
307
  .dark-theme .mermaid span {
321
- color: #d1d5db!important;
308
+ color: #d1d5db !important;
322
309
  }
323
310
 
324
311
  /* .dark-theme #mermaid-container rect {
@@ -332,38 +319,6 @@
332
319
  color: var(--gray-medium);
333
320
  }
334
321
 
335
- .dark-theme .summary-item.blue {
336
- background-color: #1e3a8a;
337
- }
338
-
339
- .dark-theme .summary-item.yellow {
340
- background-color: #78350f;
341
- }
342
-
343
- .dark-theme .summary-item.red {
344
- background-color: #7f1d1d;
345
- }
346
-
347
- .dark-theme .summary-item.green {
348
- background-color: #14532d;
349
- }
350
-
351
- .dark-theme .summary-value.blue {
352
- color: #60a5fa;
353
- }
354
-
355
- .dark-theme .summary-value.yellow {
356
- color: #facc15;
357
- }
358
-
359
- .dark-theme .summary-value.red {
360
- color: #f87171;
361
- }
362
-
363
- .dark-theme .summary-value.green {
364
- color: #34d399;
365
- }
366
-
367
322
  .dark-theme .summary-label {
368
323
  color: #9ca3af;
369
324
  }
@@ -394,8 +349,17 @@
394
349
  background-color: #454545;
395
350
  }
396
351
 
397
- .dark-theme .stage-name { color: #7eaeff; }
398
- .dark-theme .stage-mode { color: #b0b0b0; }
399
- .dark-theme .stage-func { background-color: #333; color: #ff6090; }
400
- .dark-theme .visited-mark { background-color: #4a2424; color: #ff6b6b; }
401
-
352
+ .dark-theme .stage-name {
353
+ color: #7eaeff;
354
+ }
355
+ .dark-theme .stage-mode {
356
+ color: #b0b0b0;
357
+ }
358
+ .dark-theme .stage-func {
359
+ background-color: #333;
360
+ color: #ff6090;
361
+ }
362
+ .dark-theme .visited-mark {
363
+ background-color: #4a2424;
364
+ color: #ff6b6b;
365
+ }
@@ -3,7 +3,7 @@ let refreshIntervalId = null;
3
3
 
4
4
  const refreshSelect = document.getElementById("refresh-interval");
5
5
  const themeToggleBtn = document.getElementById("theme-toggle");
6
- const shutdownBtn = document.getElementById("shutdown-btn");
6
+ // const shutdownBtn = document.getElementById("shutdown-btn");
7
7
  const tabButtons = document.querySelectorAll(".tab-btn");
8
8
  const tabContents = document.querySelectorAll(".tab-content");
9
9
 
@@ -41,13 +41,13 @@ document.addEventListener("DOMContentLoaded", async () => {
41
41
  });
42
42
  });
43
43
 
44
- shutdownBtn.addEventListener("click", async () => {
45
- if (confirm("确认要关闭 Web 服务吗?")) {
46
- const res = await fetch("/shutdown", { method: "POST" });
47
- const text = await res.text();
48
- alert(text);
49
- }
50
- });
44
+ // shutdownBtn.addEventListener("click", async () => {
45
+ // if (confirm("确认要关闭 Web 服务吗?")) {
46
+ // const res = await fetch("/shutdown", { method: "POST" });
47
+ // const text = await res.text();
48
+ // alert(text);
49
+ // }
50
+ // });
51
51
 
52
52
  // 初始化时应用之前选择的主题
53
53
  if (localStorage.getItem("theme") === "dark") {
@@ -89,6 +89,7 @@ async function refreshAll() {
89
89
  loadStructure(), // 拉取任务结构(有向图),更新 structureData
90
90
  loadErrors(), // 获取最新错误记录,更新 errors[]
91
91
  loadTopology(), // 获取最新拓扑信息,更新 TopologyData
92
+ loadSummary(), // 获取最新汇总数据,更新 summaryData
92
93
 
93
94
  pushRefreshRate(), // 每次轮询时推送刷新频率到后端
94
95
  ]);
@@ -97,11 +98,13 @@ async function refreshAll() {
97
98
  const currentStructureJSON = JSON.stringify(structureData);
98
99
  const currentErrorsJSON = JSON.stringify(errors);
99
100
  const currentTopologyJSON = JSON.stringify(topologyData);
101
+ const currentSummaryJSON = JSON.stringify(summaryData);
100
102
 
101
103
  const statusesChanged = currentStatusesJSON !== previousNodeStatusesJSON;
102
104
  const structureChanged = currentStructureJSON !== previousStructureDataJSON;
103
105
  const errorsChanged = currentErrorsJSON !== previousErrorsJSON;
104
106
  const topologyChanged = currentTopologyJSON !== previousTopologyDataJSON;
107
+ const summaryChanged = currentSummaryJSON !== previousSummaryDataJSON;
105
108
 
106
109
  if (statusesChanged || structureChanged) {
107
110
  previousNodeStatusesJSON = currentStatusesJSON;
@@ -116,12 +119,17 @@ async function refreshAll() {
116
119
  renderTopologyInfo(); // 渲染拓扑信息
117
120
  }
118
121
 
122
+ if (summaryChanged) {
123
+ previousSummaryDataJSON = currentSummaryJSON;
124
+
125
+ renderSummary(); // 右下汇总数据
126
+ }
127
+
119
128
  if (statusesChanged) {
120
129
  previousNodeStatusesJSON = currentStatusesJSON;
121
130
 
122
131
  renderDashboard(); // 中间节点状态卡片
123
132
  updateChartData(); // 右上折线图
124
- updateSummary(); // 右下汇总数据
125
133
  populateNodeFilter(); // 错误筛选器
126
134
  renderNodeList(); // 注入页节点列表
127
135
  }
@@ -50,7 +50,7 @@ function renderErrors() {
50
50
  <td class="error-message">${e.error_repr}</td>
51
51
  <td>${e.stage}</td>
52
52
  <td>${e.task_repr}</td>
53
- <td>${formatTimestamp(e.ts)}</td>
53
+ <td>${renderLocalTime(e.ts)}</td>
54
54
  `;
55
55
  errorsTableBody.appendChild(row);
56
56
  }
@@ -7,10 +7,6 @@ let hiddenNodes = new Set(
7
7
  );
8
8
 
9
9
  const dashboardGrid = document.getElementById("dashboard-grid");
10
- const totalSuccessed = document.getElementById("total-successed");
11
- const totalPending = document.getElementById("total-pending");
12
- const totalErrors = document.getElementById("total-errors");
13
- const totalNodes = document.getElementById("total-nodes");
14
10
 
15
11
  async function loadStatuses() {
16
12
  try {
@@ -122,14 +118,14 @@ function renderDashboard() {
122
118
  data.execution_mode
123
119
  }</div></div>
124
120
  </div>
125
- <div class="text-sm text-gray">开始时间: ${data.start_time}</div>
121
+ <div class="text-sm text-gray">开始时间: ${formatTimestamp(data.start_time)}</div>
126
122
  <div class="progress-container">
127
123
  <div class="progress-header">
128
124
  <span>任务完成率</span>
129
125
  <span class="time-estimate">
130
- <span class="elapsed">${data.elapsed_time}</span>
126
+ <span class="elapsed">${formatDuration(data.elapsed_time)}</span>
131
127
  &lt;
132
- <span class="remaining">${data.remaining_time}</span>,
128
+ <span class="remaining">${formatDuration(data.remaining_time)}</span>,
133
129
  <span class="task-avg-time">${data.task_avg_time}</span>,
134
130
  <span>${progress}%</span>
135
131
  </span>
@@ -143,23 +139,6 @@ function renderDashboard() {
143
139
  }
144
140
  }
145
141
 
146
- function updateSummary() {
147
- let successed = 0,
148
- pending = 0,
149
- error = 0,
150
- active = 0;
151
- Object.values(nodeStatuses).forEach((s) => {
152
- successed += s.tasks_successed;
153
- pending += s.tasks_pending;
154
- error += s.tasks_failed;
155
- if (s.status === 1) active++;
156
- });
157
- totalSuccessed.textContent = successed;
158
- totalPending.textContent = pending;
159
- totalErrors.textContent = error;
160
- totalNodes.textContent = active;
161
- }
162
-
163
142
  function initChart() {
164
143
  const ctx = document.getElementById("node-progress-chart").getContext("2d");
165
144
 
@@ -0,0 +1,36 @@
1
+ let summaryData = [];
2
+ let previousSummaryDataJSON = "";
3
+
4
+ const totalSuccessed = document.getElementById("total-successed");
5
+ const totalPending = document.getElementById("total-pending");
6
+ const totalDuplicated = document.getElementById("total-duplicated");
7
+ const totalFailed = document.getElementById("total-failed");
8
+ const totalNodes = document.getElementById("total-nodes");
9
+ const totalRemain = document.getElementById("total-remain");
10
+
11
+ async function loadSummary() {
12
+ try {
13
+ const res = await fetch("/api/get_summary");
14
+ summaryData = await res.json();
15
+ } catch (e) {
16
+ console.error("合计数据加载失败", e);
17
+ }
18
+ }
19
+
20
+ function renderSummary() {
21
+ const {
22
+ total_successed = 0,
23
+ total_pending = 0,
24
+ total_failed = 0,
25
+ total_duplicated = 0,
26
+ total_nodes = 0,
27
+ total_remain = 0,
28
+ } = summaryData || {};
29
+
30
+ totalSuccessed.textContent = total_successed;
31
+ totalPending.textContent = total_pending;
32
+ totalFailed.textContent = total_failed;
33
+ totalDuplicated.textContent = total_duplicated;
34
+ totalNodes.textContent = total_nodes;
35
+ totalRemain.textContent = formatDuration(total_remain);
36
+ }
@@ -1,5 +1,5 @@
1
1
  // task_web.js
2
- function formatTimestamp(timestamp) {
2
+ function renderLocalTime(timestamp) {
3
3
  return new Date(timestamp * 1000).toLocaleString();
4
4
  }
5
5
 
@@ -62,3 +62,36 @@ function validateJSON(text) {
62
62
  function toggleDarkTheme() {
63
63
  return document.body.classList.toggle("dark-theme");
64
64
  }
65
+
66
+ // task_statuses.js
67
+ function formatDuration(seconds) {
68
+ seconds = Math.floor(seconds);
69
+
70
+ const hours = Math.floor(seconds / 3600);
71
+ const remainder = seconds % 3600;
72
+ const minutes = Math.floor(remainder / 60);
73
+ const secs = remainder % 60;
74
+
75
+ const pad = (n) => String(n).padStart(2, "0");
76
+
77
+ if (hours > 0) {
78
+ return `${pad(hours)}:${pad(minutes)}:${pad(secs)}`;
79
+ } else {
80
+ return `${pad(minutes)}:${pad(secs)}`;
81
+ }
82
+ }
83
+
84
+ function formatTimestamp(timestamp) {
85
+ const d = new Date(timestamp * 1000);
86
+
87
+ const pad = (n) => String(n).padStart(2, "0");
88
+
89
+ const year = d.getFullYear();
90
+ const month = pad(d.getMonth() + 1);
91
+ const day = pad(d.getDate());
92
+ const hour = pad(d.getHours());
93
+ const minute = pad(d.getMinutes());
94
+ const second = pad(d.getSeconds());
95
+
96
+ return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
97
+ }