zrb 1.0.0a18__py3-none-any.whl → 1.0.0a21__py3-none-any.whl
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.
- zrb/__init__.py +5 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/api_client.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/direct_client.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/route.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase.py +1 -5
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase_factory.py +6 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/route.py +3 -9
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +44 -0
- zrb/config.py +17 -0
- zrb/input/any_input.py +4 -0
- zrb/input/base_input.py +4 -4
- zrb/input/bool_input.py +1 -1
- zrb/input/float_input.py +2 -2
- zrb/input/int_input.py +1 -1
- zrb/input/option_input.py +2 -2
- zrb/input/password_input.py +2 -2
- zrb/input/text_input.py +2 -2
- zrb/runner/cli.py +9 -34
- zrb/runner/common_util.py +31 -0
- zrb/runner/refresh-token.template.js +22 -0
- zrb/runner/web_app.py +211 -49
- zrb/runner/web_config.py +274 -0
- zrb/runner/web_controller/error_page/controller.py +27 -0
- zrb/runner/web_controller/error_page/view.html +34 -0
- zrb/runner/web_controller/group_info_page/controller.py +40 -0
- zrb/runner/web_controller/group_info_page/view.html +37 -0
- zrb/runner/web_controller/home_page/controller.py +13 -48
- zrb/runner/web_controller/home_page/view.html +30 -20
- zrb/runner/web_controller/login_page/controller.py +25 -0
- zrb/runner/web_controller/login_page/view.html +51 -0
- zrb/runner/web_controller/logout_page/controller.py +26 -0
- zrb/runner/web_controller/logout_page/view.html +41 -0
- zrb/runner/web_controller/{task_ui → session_page}/controller.py +35 -26
- zrb/runner/web_controller/{task_ui → session_page}/partial/input.html +1 -1
- zrb/runner/web_controller/session_page/view.html +92 -0
- zrb/runner/web_controller/static/common.css +11 -0
- zrb/runner/web_controller/static/login/event.js +33 -0
- zrb/runner/web_controller/static/logout/event.js +20 -0
- zrb/runner/web_controller/static/pico.min.css +1 -1
- zrb/runner/web_controller/static/session/common-util.js +63 -0
- zrb/runner/web_controller/static/session/current-session.js +164 -0
- zrb/runner/web_controller/static/session/event.js +119 -0
- zrb/runner/web_controller/static/session/past-session.js +144 -0
- zrb/runner/web_util.py +62 -4
- {zrb-1.0.0a18.dist-info → zrb-1.0.0a21.dist-info}/METADATA +9 -52
- {zrb-1.0.0a18.dist-info → zrb-1.0.0a21.dist-info}/RECORD +51 -47
- zrb/runner/web_controller/group_info_ui/controller.py +0 -83
- zrb/runner/web_controller/group_info_ui/partial/group_info.html +0 -2
- zrb/runner/web_controller/group_info_ui/partial/group_li.html +0 -1
- zrb/runner/web_controller/group_info_ui/partial/task_info.html +0 -2
- zrb/runner/web_controller/group_info_ui/partial/task_li.html +0 -1
- zrb/runner/web_controller/group_info_ui/view.html +0 -31
- zrb/runner/web_controller/home_page/partial/group_info.html +0 -2
- zrb/runner/web_controller/home_page/partial/group_li.html +0 -1
- zrb/runner/web_controller/home_page/partial/task_info.html +0 -2
- zrb/runner/web_controller/home_page/partial/task_li.html +0 -1
- zrb/runner/web_controller/task_ui/__init__.py +0 -0
- zrb/runner/web_controller/task_ui/partial/common-util.js +0 -37
- zrb/runner/web_controller/task_ui/partial/main.js +0 -195
- zrb/runner/web_controller/task_ui/partial/show-existing-session.js +0 -97
- zrb/runner/web_controller/task_ui/partial/visualize-history.js +0 -104
- zrb/runner/web_controller/task_ui/view.html +0 -87
- /zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/{factory.py → user_repository_factory.py} +0 -0
- /zrb/{builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository → runner/web_controller/group_info_page}/__init__.py +0 -0
- /zrb/runner/web_controller/{group_info_ui → session_page}/__init__.py +0 -0
- {zrb-1.0.0a18.dist-info → zrb-1.0.0a21.dist-info}/WHEEL +0 -0
- {zrb-1.0.0a18.dist-info → zrb-1.0.0a21.dist-info}/entry_points.txt +0 -0
@@ -1,83 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
|
3
|
-
from zrb.group.any_group import AnyGroup
|
4
|
-
from zrb.util.file import read_file
|
5
|
-
from zrb.util.group import get_non_empty_subgroups, get_subtasks
|
6
|
-
from zrb.util.string.format import fstring_format
|
7
|
-
|
8
|
-
_DIR = os.path.dirname(__file__)
|
9
|
-
|
10
|
-
_VIEW_TEMPLATE = read_file(os.path.join(_DIR, "view.html"))
|
11
|
-
_GROUP_INFO_TEMPLATE = read_file(os.path.join(_DIR, "partial", "group_info.html"))
|
12
|
-
_GROUP_LI_TEMPLATE = read_file(os.path.join(_DIR, "partial", "group_li.html"))
|
13
|
-
_TASK_INFO_TEMPLATE = read_file(os.path.join(_DIR, "partial", "task_info.html"))
|
14
|
-
_TASK_LI_TEMPLATE = read_file(os.path.join(_DIR, "partial", "task_li.html"))
|
15
|
-
|
16
|
-
|
17
|
-
def handle_group_info_ui(root_group: AnyGroup, group: AnyGroup, url: str):
|
18
|
-
from fastapi.responses import HTMLResponse
|
19
|
-
|
20
|
-
url_parts = url.split("/")
|
21
|
-
parent_url_parts = url_parts[:-2] + [""]
|
22
|
-
parent_url = "/".join(parent_url_parts)
|
23
|
-
subgroups = get_non_empty_subgroups(group, web_only=True)
|
24
|
-
group_info = (
|
25
|
-
""
|
26
|
-
if len(subgroups) == 0
|
27
|
-
else fstring_format(
|
28
|
-
_GROUP_INFO_TEMPLATE,
|
29
|
-
{
|
30
|
-
"group_li": "\n".join(
|
31
|
-
[
|
32
|
-
fstring_format(
|
33
|
-
_GROUP_LI_TEMPLATE,
|
34
|
-
{
|
35
|
-
"caption": name,
|
36
|
-
"url": url,
|
37
|
-
"description": group.description,
|
38
|
-
},
|
39
|
-
)
|
40
|
-
for name, group in subgroups.items()
|
41
|
-
]
|
42
|
-
)
|
43
|
-
},
|
44
|
-
)
|
45
|
-
)
|
46
|
-
subtasks = get_subtasks(group, web_only=True)
|
47
|
-
task_info = (
|
48
|
-
""
|
49
|
-
if len(subtasks) == 0
|
50
|
-
else fstring_format(
|
51
|
-
_TASK_INFO_TEMPLATE,
|
52
|
-
{
|
53
|
-
"task_li": "\n".join(
|
54
|
-
[
|
55
|
-
fstring_format(
|
56
|
-
_TASK_LI_TEMPLATE,
|
57
|
-
{
|
58
|
-
"caption": name,
|
59
|
-
"url": url,
|
60
|
-
"description": task.description,
|
61
|
-
},
|
62
|
-
)
|
63
|
-
for name, task in subtasks.items()
|
64
|
-
]
|
65
|
-
)
|
66
|
-
},
|
67
|
-
)
|
68
|
-
)
|
69
|
-
return HTMLResponse(
|
70
|
-
fstring_format(
|
71
|
-
_VIEW_TEMPLATE,
|
72
|
-
{
|
73
|
-
"group_info": group_info,
|
74
|
-
"task_info": task_info,
|
75
|
-
"name": group.name,
|
76
|
-
"description": group.description,
|
77
|
-
"root_name": root_group.name,
|
78
|
-
"root_description": root_group.description,
|
79
|
-
"url": url,
|
80
|
-
"parent_url": parent_url,
|
81
|
-
},
|
82
|
-
)
|
83
|
-
)
|
@@ -1 +0,0 @@
|
|
1
|
-
<li><a href="{url}{caption}">{caption}</a> {description}</li>
|
@@ -1 +0,0 @@
|
|
1
|
-
<li><a href="{url}{caption}">{caption}</a> {description}</li>
|
@@ -1,31 +0,0 @@
|
|
1
|
-
<!doctype html>
|
2
|
-
<html lang="en">
|
3
|
-
<head>
|
4
|
-
<meta charset="utf-8">
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
|
-
<meta name="color-scheme" content="light dark">
|
7
|
-
<link rel="stylesheet" href="/static/pico.min.css">
|
8
|
-
<link rel="icon" href="/static/favicon-32x32.png" sizes="32x32" type="image/png">
|
9
|
-
<title>Zrb</title>
|
10
|
-
</head>
|
11
|
-
<body>
|
12
|
-
<header class="container">
|
13
|
-
<hgroup>
|
14
|
-
<h1>{root_name}</h1>
|
15
|
-
<p>{root_description}</p>
|
16
|
-
<nav>
|
17
|
-
<ul>
|
18
|
-
<li><a href="/">🏠 Home</a></li>
|
19
|
-
<li><a href="{parent_url}">🔙 Parent</a></li>
|
20
|
-
</ul>
|
21
|
-
</nav>
|
22
|
-
</hgroup>
|
23
|
-
</header>
|
24
|
-
<main class="container">
|
25
|
-
<h3>{name}</h3>
|
26
|
-
<p>{description}</p>
|
27
|
-
{group_info}
|
28
|
-
{task_info}
|
29
|
-
</main>
|
30
|
-
</body>
|
31
|
-
</html>
|
@@ -1 +0,0 @@
|
|
1
|
-
<li><a href="/ui/{caption}">{caption}</a> {description}</li>
|
@@ -1 +0,0 @@
|
|
1
|
-
<li><a href="/ui/{caption}">{caption}</a> {description}</li>
|
File without changes
|
@@ -1,37 +0,0 @@
|
|
1
|
-
function getFinalColor(finalStatus) {
|
2
|
-
switch(finalStatus) {
|
3
|
-
case "started":
|
4
|
-
return "#3498db";
|
5
|
-
case "ready":
|
6
|
-
return "#f39c12";
|
7
|
-
case "completed":
|
8
|
-
return "#2ecc71";
|
9
|
-
case "skipped":
|
10
|
-
return "#95a5a6";
|
11
|
-
case "failed":
|
12
|
-
return "#e74c3c";
|
13
|
-
case "permanently-failed":
|
14
|
-
return "#c0392b";
|
15
|
-
case "terminated":
|
16
|
-
return "#ffffff";
|
17
|
-
default:
|
18
|
-
return "#ffffff";
|
19
|
-
}
|
20
|
-
}
|
21
|
-
|
22
|
-
function getFinalTaskStatus(taskStatus) {
|
23
|
-
if (taskStatus.is_completed) {
|
24
|
-
return "completed";
|
25
|
-
}
|
26
|
-
if (taskStatus.is_terminated) {
|
27
|
-
return "terminated"
|
28
|
-
}
|
29
|
-
if (taskStatus.is_permanently_failed) {
|
30
|
-
return "permanently-failed"
|
31
|
-
}
|
32
|
-
const histories = taskStatus.history;
|
33
|
-
if (histories.length > 0) {
|
34
|
-
return histories[histories.length - 1].status;
|
35
|
-
}
|
36
|
-
return "";
|
37
|
-
}
|
@@ -1,195 +0,0 @@
|
|
1
|
-
window.addEventListener("load", async function () {
|
2
|
-
// Get current session
|
3
|
-
if (SESSION_NAME != "") {
|
4
|
-
pollSession();
|
5
|
-
}
|
6
|
-
// set maxStartDate to today
|
7
|
-
const tomorrow = new Date();
|
8
|
-
tomorrow.setDate(tomorrow.getDate() + 1); // Move to the next day
|
9
|
-
tomorrow.setHours(0, 0, 0, 0);
|
10
|
-
const formattedTomorrow = toLocalDateInputValue(tomorrow);
|
11
|
-
const maxStartAtInput = document.getElementById("max-start-at-input");
|
12
|
-
maxStartAtInput.value = formattedTomorrow;
|
13
|
-
// set minStartDate to yesterday
|
14
|
-
const today = new Date();
|
15
|
-
today.setHours(0, 0, 0, 0); // Set time to 00:00:00
|
16
|
-
const formattedToday = toLocalDateInputValue(today);
|
17
|
-
const minStartAtInput = document.getElementById("min-start-at-input");
|
18
|
-
minStartAtInput.value = formattedToday;
|
19
|
-
// Update session
|
20
|
-
pollExistingSessions(PAGE);
|
21
|
-
});
|
22
|
-
|
23
|
-
async function pollExistingSessions() {
|
24
|
-
while (true) {
|
25
|
-
await getExistingSessions(PAGE);
|
26
|
-
await delay(5000);
|
27
|
-
}
|
28
|
-
}
|
29
|
-
|
30
|
-
async function getExistingSessions(page) {
|
31
|
-
PAGE=page
|
32
|
-
const minStartAtInput = document.getElementById("min-start-at-input");
|
33
|
-
const minStartAt = formatDate(minStartAtInput.value);
|
34
|
-
const maxStartAtInput = document.getElementById("max-start-at-input");
|
35
|
-
const maxStartAt = formatDate(maxStartAtInput.value);
|
36
|
-
console.log(minStartAt);
|
37
|
-
const queryString = new URLSearchParams({
|
38
|
-
page: page,
|
39
|
-
from: minStartAt,
|
40
|
-
to: maxStartAt,
|
41
|
-
}).toString();
|
42
|
-
try {
|
43
|
-
// Send the AJAX request
|
44
|
-
const response = await fetch(`${API_URL}list?${queryString}`, {
|
45
|
-
method: "GET",
|
46
|
-
headers: {
|
47
|
-
"Content-Type": "application/json"
|
48
|
-
},
|
49
|
-
});
|
50
|
-
const {total, data} = await response.json();
|
51
|
-
showExistingSession(page, total, data);
|
52
|
-
console.log("Success:", data);
|
53
|
-
} catch (error) {
|
54
|
-
console.error("Error:", error);
|
55
|
-
}
|
56
|
-
}
|
57
|
-
|
58
|
-
|
59
|
-
function toLocalDateInputValue(date) {
|
60
|
-
const year = date.getFullYear();
|
61
|
-
const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed
|
62
|
-
const day = String(date.getDate()).padStart(2, '0');
|
63
|
-
const hours = String(date.getHours()).padStart(2, '0');
|
64
|
-
const minutes = String(date.getMinutes()).padStart(2, '0');
|
65
|
-
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
66
|
-
}
|
67
|
-
|
68
|
-
|
69
|
-
function formatDate(dateString) {
|
70
|
-
const date = new Date(dateString);
|
71
|
-
const year = date.getFullYear();
|
72
|
-
const month = String(date.getMonth() + 1).padStart(2, '0');
|
73
|
-
const day = String(date.getDate()).padStart(2, '0');
|
74
|
-
const hours = String(date.getHours()).padStart(2, '0');
|
75
|
-
const minutes = String(date.getMinutes()).padStart(2, '0');
|
76
|
-
const seconds = String(date.getSeconds()).padStart(2, '0');
|
77
|
-
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
78
|
-
}
|
79
|
-
|
80
|
-
function openDialog(event) {
|
81
|
-
event.preventDefault();
|
82
|
-
const dialog = document.getElementById("session-history-dialog")
|
83
|
-
dialog.showModal();
|
84
|
-
}
|
85
|
-
|
86
|
-
|
87
|
-
function closeDialog(event) {
|
88
|
-
event.preventDefault();
|
89
|
-
const dialog = document.getElementById("session-history-dialog")
|
90
|
-
dialog.close();
|
91
|
-
}
|
92
|
-
|
93
|
-
|
94
|
-
async function submitForm(event) {
|
95
|
-
// Prevent the form from submitting the traditional way
|
96
|
-
event.preventDefault();
|
97
|
-
// Select the form
|
98
|
-
const form = document.getElementById("submit-task-form");
|
99
|
-
// Initialize an empty object to hold form data
|
100
|
-
const formData = {};
|
101
|
-
// Iterate through each input in the form
|
102
|
-
Array.from(form.elements).forEach(element => {
|
103
|
-
// Only include inputs with a name attribute and ignore buttons
|
104
|
-
if (element.name && (element.type !== "button" && element.type !== "submit")) {
|
105
|
-
formData[element.name] = element.value;
|
106
|
-
}
|
107
|
-
});
|
108
|
-
// Convert formData to JSON
|
109
|
-
const jsonData = JSON.stringify(formData);
|
110
|
-
try {
|
111
|
-
// Send the AJAX request
|
112
|
-
const response = await fetch(API_URL, {
|
113
|
-
method: "POST",
|
114
|
-
headers: {
|
115
|
-
"Content-Type": "application/json"
|
116
|
-
},
|
117
|
-
body: jsonData
|
118
|
-
});
|
119
|
-
const data = await response.json();
|
120
|
-
console.log("Success:", data);
|
121
|
-
SESSION_NAME = data.session_name;
|
122
|
-
history.pushState(null, "", `${CURRENT_URL}${SESSION_NAME}`);
|
123
|
-
getExistingSessions(0);
|
124
|
-
await pollSession();
|
125
|
-
} catch (error) {
|
126
|
-
console.error("Error:", error);
|
127
|
-
}
|
128
|
-
}
|
129
|
-
|
130
|
-
|
131
|
-
async function pollSession() {
|
132
|
-
const resultTextarea = document.getElementById("result-textarea");
|
133
|
-
const logTextarea = document.getElementById("log-textarea");
|
134
|
-
const submitTaskForm = document.getElementById("submit-task-form");
|
135
|
-
let isFinished = false;
|
136
|
-
let errorCount = 0;
|
137
|
-
while (!isFinished) {
|
138
|
-
try {
|
139
|
-
const data = await getSession();
|
140
|
-
// update inputs
|
141
|
-
const dataInputs = data.input;
|
142
|
-
for (const inputName in dataInputs) {
|
143
|
-
const inputValue = dataInputs[inputName];
|
144
|
-
const input = submitTaskForm.querySelector(`[name="${inputName}"]`);
|
145
|
-
input.value = inputValue;
|
146
|
-
}
|
147
|
-
resultLineCount = data.final_result.split("\n").length;
|
148
|
-
resultTextarea.rows = resultLineCount <= 5 ? resultLineCount : 5;
|
149
|
-
// update text areas
|
150
|
-
resultTextarea.value = data.final_result;
|
151
|
-
logTextarea.value = data.log.join("\n");
|
152
|
-
logTextarea.scrollTop = logTextarea.scrollHeight;
|
153
|
-
// visualize history
|
154
|
-
visualizeHistory(data.task_status, data.finished);
|
155
|
-
if (data.finished) {
|
156
|
-
isFinished = true;
|
157
|
-
} else {
|
158
|
-
await delay(200);
|
159
|
-
}
|
160
|
-
} catch (error) {
|
161
|
-
console.error("Error fetching session status:", error);
|
162
|
-
errorCount++;
|
163
|
-
if (errorCount > 5) {
|
164
|
-
console.error("Exceeding maximum error count, quitting");
|
165
|
-
return;
|
166
|
-
}
|
167
|
-
continue;
|
168
|
-
}
|
169
|
-
}
|
170
|
-
}
|
171
|
-
|
172
|
-
|
173
|
-
async function getSession() {
|
174
|
-
try {
|
175
|
-
const response = await fetch(`${API_URL}${SESSION_NAME}`, {
|
176
|
-
method: "GET",
|
177
|
-
headers: {
|
178
|
-
"Content-Type": "application/json"
|
179
|
-
},
|
180
|
-
});
|
181
|
-
return await response.json();
|
182
|
-
} catch (error) {
|
183
|
-
console.error("Error:", error);
|
184
|
-
}
|
185
|
-
}
|
186
|
-
|
187
|
-
|
188
|
-
function rstripSlash(str) {
|
189
|
-
return str.replace(/\/+$/, ""); // Removes one or more trailing slashes
|
190
|
-
}
|
191
|
-
|
192
|
-
|
193
|
-
function delay(ms) {
|
194
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
195
|
-
}
|
@@ -1,97 +0,0 @@
|
|
1
|
-
function showExistingSession(page, total, data) {
|
2
|
-
const ul = document.getElementById("session-history-ul");
|
3
|
-
ul.innerHTML = ''; // Clear existing content
|
4
|
-
data.forEach(item => {
|
5
|
-
const taskStatus = item.task_status[item.main_task_name];
|
6
|
-
const finalStatus = getFinalTaskStatus(taskStatus);
|
7
|
-
const finalColor = getFinalColor(finalStatus);
|
8
|
-
const taskHistories = taskStatus.history;
|
9
|
-
const taskStartTime = taskHistories.length > 0 ? taskHistories[0].time : ""
|
10
|
-
const li = document.createElement('li');
|
11
|
-
const a = document.createElement('a');
|
12
|
-
const dateSpan = document.createElement('span');
|
13
|
-
const statusSpan = document.createElement('span');
|
14
|
-
a.textContent = item.name;
|
15
|
-
a.target = '_blank';
|
16
|
-
a.href = `${UI_URL}${item.name}`;
|
17
|
-
li.appendChild(a);
|
18
|
-
statusSpan.style.marginLeft = "10px";
|
19
|
-
statusSpan.style.display = 'inline-block';
|
20
|
-
statusSpan.style.width = '15px';
|
21
|
-
statusSpan.style.height = '15px';
|
22
|
-
statusSpan.style.borderRadius = '50%';
|
23
|
-
statusSpan.style.border = '2px solid black';
|
24
|
-
statusSpan.style.backgroundColor = finalColor;
|
25
|
-
li.appendChild(statusSpan);
|
26
|
-
dateSpan.style.marginLeft = "10px";
|
27
|
-
dateSpan.textContent = taskStartTime;
|
28
|
-
li.appendChild(dateSpan);
|
29
|
-
ul.appendChild(li);
|
30
|
-
});
|
31
|
-
const paginationUl = document.getElementById("session-history-pagination-ul");
|
32
|
-
paginationUl.innerHTML = ''; // Clear previous pagination
|
33
|
-
|
34
|
-
const totalPages = Math.ceil(total / 10); // Calculate total pages based on page size
|
35
|
-
const maxPagesToShow = 5; // Number of pages to display around the current page
|
36
|
-
const halfMaxPages = Math.floor(maxPagesToShow / 2);
|
37
|
-
|
38
|
-
// Add first page and previous controls
|
39
|
-
if (page > 0) {
|
40
|
-
paginationUl.appendChild(createPageLink("⟪", 0)); // Go to first page
|
41
|
-
paginationUl.appendChild(createPageLink("⟨", page - 1)); // Go to previous page
|
42
|
-
}
|
43
|
-
|
44
|
-
let startPage = Math.max(0, page - halfMaxPages);
|
45
|
-
let endPage = Math.min(totalPages - 1, page + halfMaxPages);
|
46
|
-
|
47
|
-
if (page <= halfMaxPages) {
|
48
|
-
endPage = Math.min(totalPages - 1, maxPagesToShow - 1);
|
49
|
-
} else if (page + halfMaxPages >= totalPages) {
|
50
|
-
startPage = Math.max(0, totalPages - maxPagesToShow);
|
51
|
-
}
|
52
|
-
|
53
|
-
if (startPage > 1) {
|
54
|
-
paginationUl.appendChild(createEllipsis());
|
55
|
-
}
|
56
|
-
|
57
|
-
// Add page links within the calculated range
|
58
|
-
for (let i = startPage; i <= endPage; i++) {
|
59
|
-
if (i === page) {
|
60
|
-
const pageLi = document.createElement('li');
|
61
|
-
pageLi.textContent = i + 1;
|
62
|
-
paginationUl.append(pageLi)
|
63
|
-
} else {
|
64
|
-
paginationUl.appendChild(createPageLink(i + 1, i))
|
65
|
-
}
|
66
|
-
}
|
67
|
-
|
68
|
-
// Add ellipsis after the end page if needed
|
69
|
-
if (endPage < totalPages - 2) {
|
70
|
-
paginationUl.appendChild(createEllipsis());
|
71
|
-
}
|
72
|
-
|
73
|
-
// Add next and last page controls
|
74
|
-
if (page < totalPages - 1) {
|
75
|
-
paginationUl.appendChild(createPageLink("⟩", page + 1)); // Go to next page
|
76
|
-
paginationUl.appendChild(createPageLink("⟫", totalPages - 1)); // Go to last page
|
77
|
-
}
|
78
|
-
}
|
79
|
-
|
80
|
-
function createPageLink(text, page) {
|
81
|
-
const li = document.createElement('li');
|
82
|
-
const link = document.createElement('a');
|
83
|
-
link.textContent = text;
|
84
|
-
link.href = '#';
|
85
|
-
link.onclick = (e) => {
|
86
|
-
e.preventDefault();
|
87
|
-
getExistingSessions(page);
|
88
|
-
};
|
89
|
-
li.appendChild(link);
|
90
|
-
return li;
|
91
|
-
}
|
92
|
-
|
93
|
-
function createEllipsis() {
|
94
|
-
const li = document.createElement('li');
|
95
|
-
li.textContent = '...';
|
96
|
-
return li;
|
97
|
-
}
|
@@ -1,104 +0,0 @@
|
|
1
|
-
function visualizeHistory(allTaskStatus, finished) {
|
2
|
-
const taskNames = Object.keys(allTaskStatus);
|
3
|
-
const now = Date.now();
|
4
|
-
|
5
|
-
// Set up canvas context
|
6
|
-
const canvas = document.getElementById("history-canvas");
|
7
|
-
canvas.height = taskNames.length * 50 + 10;
|
8
|
-
const ctx = canvas.getContext("2d");
|
9
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
10
|
-
ctx.fillStyle = "#EEE";
|
11
|
-
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
12
|
-
|
13
|
-
// Calculate start and end times
|
14
|
-
let minDateTime = null;
|
15
|
-
let maxDateTime = null;
|
16
|
-
for (let taskName in allTaskStatus) {
|
17
|
-
let history = allTaskStatus[taskName].history;
|
18
|
-
if (history.length == 0) {
|
19
|
-
continue;
|
20
|
-
}
|
21
|
-
let startTime = new Date(history[0].time);
|
22
|
-
if (minDateTime === null || minDateTime > startTime) {
|
23
|
-
minDateTime = startTime;
|
24
|
-
}
|
25
|
-
let lastTime = new Date(history[history.length - 1].time);
|
26
|
-
if (maxDateTime === null || maxDateTime < lastTime) {
|
27
|
-
maxDateTime = lastTime;
|
28
|
-
}
|
29
|
-
}
|
30
|
-
if (minDateTime === null || maxDateTime === null) {
|
31
|
-
return
|
32
|
-
}
|
33
|
-
if (!finished) {
|
34
|
-
maxDateTime = now;
|
35
|
-
}
|
36
|
-
if (maxDateTime - minDateTime == 0) {
|
37
|
-
maxDateTime.setSeconds(maxDateTime.getSeconds() + 1)
|
38
|
-
}
|
39
|
-
// Canvas settings
|
40
|
-
const chartWidth = canvas.width;
|
41
|
-
const barHeight = 20;
|
42
|
-
const gap = 10;
|
43
|
-
const timeScale = (chartWidth - 200) / (maxDateTime - minDateTime);
|
44
|
-
|
45
|
-
// Draw labels and bars
|
46
|
-
taskNames.forEach((taskName, index) => {
|
47
|
-
const taskHistories = allTaskStatus[taskName].history;
|
48
|
-
if (taskHistories.length == 0) {
|
49
|
-
return
|
50
|
-
}
|
51
|
-
// Get last status
|
52
|
-
const finalStatus = getFinalTaskStatus(allTaskStatus[taskName]);
|
53
|
-
const startDateTime = new Date(taskHistories[0].time);
|
54
|
-
const endDateTime = getTaskEndDateTime(allTaskStatus[taskName], now);
|
55
|
-
const startX = 100 + (startDateTime - minDateTime) * timeScale;
|
56
|
-
const barWidth = (endDateTime - startDateTime) * timeScale;
|
57
|
-
const startY = index * (barHeight + gap + 20) + 5;
|
58
|
-
|
59
|
-
// Draw task label
|
60
|
-
ctx.fillStyle = "#000";
|
61
|
-
ctx.font = "12px Arial";
|
62
|
-
ctx.textAlign = "right";
|
63
|
-
ctx.fillText(taskName, 90, startY + barHeight / 1.5);
|
64
|
-
|
65
|
-
// Draw task bar
|
66
|
-
ctx.fillStyle = getFinalColor(finalStatus);
|
67
|
-
ctx.fillRect(startX, startY, barWidth, barHeight);
|
68
|
-
|
69
|
-
ctx.fillStyle = "#000";
|
70
|
-
ctx.textAlign = "left";
|
71
|
-
// Combine captions if time overlap
|
72
|
-
let labels = {};
|
73
|
-
for (let taskHistory of taskHistories) {
|
74
|
-
let status = taskHistory.status;
|
75
|
-
let dateTime = new Date(taskHistory.time);
|
76
|
-
let statusStartX = 100 + (dateTime - minDateTime) * timeScale;
|
77
|
-
if (!(statusStartX in labels)) {
|
78
|
-
labels[statusStartX] = {dateTime: dateTime, caption: status};
|
79
|
-
} else {
|
80
|
-
labels[statusStartX].caption += `, ${status}`
|
81
|
-
}
|
82
|
-
}
|
83
|
-
// Draw start and end time below the bar
|
84
|
-
for (let statusStartX in labels) {
|
85
|
-
const {dateTime, caption} = labels[statusStartX];
|
86
|
-
const [dateStr, timeStr] = dateTime.toISOString().split("T");
|
87
|
-
ctx.font = "10px Arial";
|
88
|
-
ctx.fillText(caption, statusStartX, startY + 10);
|
89
|
-
ctx.font = "8px Arial";
|
90
|
-
ctx.fillText(dateStr, statusStartX, startY + barHeight + 10);
|
91
|
-
ctx.fillText(timeStr, statusStartX, startY + barHeight + 20);
|
92
|
-
}
|
93
|
-
});
|
94
|
-
}
|
95
|
-
|
96
|
-
function getTaskEndDateTime(taskStatus, now) {
|
97
|
-
if (taskStatus.is_completed || taskStatus.is_terminated || taskStatus.is_skipped || taskStatus.is_permanently_failed) {
|
98
|
-
histories = taskStatus.history;
|
99
|
-
if (histories.length > 0) {
|
100
|
-
return new Date(histories[histories.length - 1].time);
|
101
|
-
}
|
102
|
-
}
|
103
|
-
return now;
|
104
|
-
}
|
@@ -1,87 +0,0 @@
|
|
1
|
-
<!doctype html>
|
2
|
-
<html lang="en">
|
3
|
-
<head>
|
4
|
-
<meta charset="utf-8">
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
|
-
<meta name="color-scheme" content="light dark">
|
7
|
-
<link rel="stylesheet" href="/static/pico.min.css">
|
8
|
-
<link rel="icon" href="/static/favicon-32x32.png" sizes="32x32" type="image/png">
|
9
|
-
<title>Zrb</title>
|
10
|
-
</head>
|
11
|
-
<body>
|
12
|
-
|
13
|
-
<header class="container">
|
14
|
-
<hgroup>
|
15
|
-
<h1>{root_name}</h1>
|
16
|
-
<p>{root_description}</p>
|
17
|
-
<nav>
|
18
|
-
<ul>
|
19
|
-
<li><a href="/">🏠 Home</a></li>
|
20
|
-
<li><a href="{parent_url}">🔙 Parent</a></li>
|
21
|
-
<li><a href="{ui_url}">🆕 New Session</a></li>
|
22
|
-
<li><a href="#" onclick="openDialog(event)">📂 Existing Session</a></li>
|
23
|
-
</ul>
|
24
|
-
</nav>
|
25
|
-
</hgroup>
|
26
|
-
</header>
|
27
|
-
|
28
|
-
<dialog id="session-history-dialog">
|
29
|
-
<article>
|
30
|
-
<header>
|
31
|
-
<button aria-label="Close" rel="prev" onclick="closeDialog(event)"></button>
|
32
|
-
<p>
|
33
|
-
<strong>📂 Existing Session</strong>
|
34
|
-
</p>
|
35
|
-
</header>
|
36
|
-
<main>
|
37
|
-
<form>
|
38
|
-
<fieldset class="grid">
|
39
|
-
<input type="datetime-local" id="min-start-at-input" placeholder="Minimum Start Time" aria-label="Minimum Start Time" />
|
40
|
-
<input type="datetime-local" id="max-start-at-input" placeholder="Maximum Start Time" aria-label="Maximum Start Time" />
|
41
|
-
</fieldset>
|
42
|
-
</form>
|
43
|
-
<ul id="session-history-ul" style="font-family:monospace;"></ul>
|
44
|
-
</main>
|
45
|
-
<footer>
|
46
|
-
<nav>
|
47
|
-
<ul id="session-history-pagination-ul"></ul>
|
48
|
-
</nav>
|
49
|
-
</footer>
|
50
|
-
</article>
|
51
|
-
</dialog>
|
52
|
-
|
53
|
-
<main class="container">
|
54
|
-
<h3>{name}</h3>
|
55
|
-
<p>{description}</p>
|
56
|
-
<canvas id="history-canvas" height="0" width="1000" style="width: 100%;"></canvas>
|
57
|
-
<hr />
|
58
|
-
<form id="submit-task-form" onsubmit="submitForm(event)">
|
59
|
-
{task_inputs}
|
60
|
-
<button>🚀 Run</button>
|
61
|
-
</form>
|
62
|
-
<hr />
|
63
|
-
<label>
|
64
|
-
Result
|
65
|
-
<textarea id="result-textarea" rows="1" readonly style="font-family:monospace;">
|
66
|
-
</textarea>
|
67
|
-
</label>
|
68
|
-
<label>
|
69
|
-
Log
|
70
|
-
<textarea id="log-textarea" rows="5" readonly style="font-family:monospace;">
|
71
|
-
</textarea>
|
72
|
-
</label>
|
73
|
-
</main>
|
74
|
-
|
75
|
-
</body>
|
76
|
-
<script>
|
77
|
-
const CURRENT_URL = '{url}';
|
78
|
-
const API_URL = '{api_url}';
|
79
|
-
const UI_URL = `{ui_url}`;
|
80
|
-
let SESSION_NAME = '{session_name}';
|
81
|
-
let PAGE = 0;
|
82
|
-
{main_script}
|
83
|
-
{common_util_script}
|
84
|
-
{show_existing_session_script}
|
85
|
-
{visualize_history_script}
|
86
|
-
</script>
|
87
|
-
</html>
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|