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.
- {celestialflow-3.0.9 → celestialflow-3.1.0}/PKG-INFO +14 -13
- {celestialflow-3.0.9 → celestialflow-3.1.0}/README.md +13 -12
- {celestialflow-3.0.9 → celestialflow-3.1.0}/pyproject.toml +1 -1
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/__init__.py +2 -2
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/css/base.css +34 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/css/dashboard.css +34 -70
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/main.js +17 -9
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/task_errors.js +1 -1
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/task_statuses.js +3 -24
- celestialflow-3.1.0/src/celestialflow/static/js/task_summary.js +36 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/utils.js +34 -1
- celestialflow-3.0.9/src/celestialflow/task_manage.py → celestialflow-3.1.0/src/celestialflow/task_executor.py +108 -88
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_graph.py +95 -115
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_logging.py +32 -8
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_nodes.py +4 -7
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_progress.py +16 -16
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_queue.py +56 -84
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_report.py +103 -19
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_stage.py +54 -9
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_tools.py +166 -34
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_types.py +40 -16
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_web.py +68 -12
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/templates/index.html +17 -8
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow.egg-info/PKG-INFO +14 -13
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow.egg-info/SOURCES.txt +3 -2
- celestialflow-3.0.9/tests/test_manage.py → celestialflow-3.1.0/tests/test_executor.py +24 -22
- {celestialflow-3.0.9 → celestialflow-3.1.0}/tests/test_nodes.py +4 -1
- {celestialflow-3.0.9 → celestialflow-3.1.0}/setup.cfg +0 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/css/errors.css +0 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/css/inject.css +0 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/favicon.ico +0 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/task_injection.js +0 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/task_structure.js +0 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/static/js/task_topology.js +0 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow/task_structure.py +0 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow.egg-info/dependency_links.txt +0 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow.egg-info/entry_points.txt +0 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow.egg-info/requires.txt +0 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/src/celestialflow.egg-info/top_level.txt +0 -0
- {celestialflow-3.0.9 → celestialflow-3.1.0}/tests/test_graph.py +0 -0
- {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
|
|
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
|
-
框架的基本单元为 **
|
|
52
|
+
框架的基本单元为 **TaskExecutor**,可独立运行,并支持四种执行模式:
|
|
53
53
|
|
|
54
54
|
* **线性(serial)**
|
|
55
55
|
* **多线程(thread)**
|
|
56
56
|
* **多进程(process)**
|
|
57
57
|
* **协程(async)**
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
TaskExecutor 实现了对任务的结果缓存,任务去重,进度条显示,多执行模式比较等功能,单独使用也很好用。
|
|
60
60
|
|
|
61
|
-
但除去直接使用
|
|
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
|
|
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 =
|
|
150
|
-
stage2 =
|
|
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
|
-
- [🔧
|
|
176
|
-
- [
|
|
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[
|
|
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从
|
|
346
|
+
- 3.0.7: 将TaskStage从TaskExecutor中单独抽出来作为一个子类; 增加新节点TaskRouter, 可以将传入的任务选择的传给不同的下游节点, 而不是进行广播
|
|
346
347
|
- 3.0.8: 在ctree逻辑上将"任务重试"事件后的"任务成功/失败/重试"事件视为因果关系, 而非之前的并行关系; 重构错误搜集部分逻辑; 修复大量3.0.6与3.07版本引入的bug; 优化部分log表现
|
|
347
|
-
- 3.0.9:
|
|
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
|
-
框架的基本单元为 **
|
|
26
|
+
框架的基本单元为 **TaskExecutor**,可独立运行,并支持四种执行模式:
|
|
27
27
|
|
|
28
28
|
* **线性(serial)**
|
|
29
29
|
* **多线程(thread)**
|
|
30
30
|
* **多进程(process)**
|
|
31
31
|
* **协程(async)**
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
TaskExecutor 实现了对任务的结果缓存,任务去重,进度条显示,多执行模式比较等功能,单独使用也很好用。
|
|
34
34
|
|
|
35
|
-
但除去直接使用
|
|
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
|
|
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 =
|
|
124
|
-
stage2 =
|
|
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
|
-
- [🔧
|
|
150
|
-
- [
|
|
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[
|
|
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从
|
|
320
|
+
- 3.0.7: 将TaskStage从TaskExecutor中单独抽出来作为一个子类; 增加新节点TaskRouter, 可以将传入的任务选择的传给不同的下游节点, 而不是进行广播
|
|
320
321
|
- 3.0.8: 在ctree逻辑上将"任务重试"事件后的"任务成功/失败/重试"事件视为因果关系, 而非之前的并行关系; 重构错误搜集部分逻辑; 修复大量3.0.6与3.07版本引入的bug; 优化部分log表现
|
|
321
|
-
- 3.0.9:
|
|
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, 用于提醒不规范设置;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from .task_graph import TaskGraph
|
|
2
|
-
from .
|
|
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
|
-
"
|
|
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 {
|
|
178
|
-
|
|
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
|
-
|
|
253
|
-
background-color:
|
|
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
|
-
|
|
266
|
-
color:
|
|
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 {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
.dark-theme .
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
}
|
|
@@ -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
|
<
|
|
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
|
|
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
|
+
}
|