django-spire 0.23.8__py3-none-any.whl → 0.23.10__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.
- django_spire/consts.py +1 -1
- django_spire/contrib/progress/__init__.py +7 -13
- django_spire/contrib/progress/enums.py +2 -1
- django_spire/contrib/progress/session.py +283 -0
- django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +35 -57
- django_spire/contrib/progress/tasks.py +67 -0
- django_spire/contrib/progress/templates/django_spire/contrib/progress/card/card.html +25 -24
- django_spire/contrib/progress/templates/django_spire/contrib/progress/modal/content.html +58 -67
- django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template +2 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +2 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +2 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +2 -0
- django_spire/core/templates/django_spire/card/card.html +1 -1
- django_spire/core/templates/django_spire/card/title_card.html +7 -4
- django_spire/core/templates/django_spire/table/base.html +4 -4
- {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/METADATA +2 -2
- {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/RECORD +20 -23
- {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/licenses/LICENSE.md +1 -1
- django_spire/contrib/progress/mixins.py +0 -36
- django_spire/contrib/progress/runner.py +0 -140
- django_spire/contrib/progress/states.py +0 -64
- django_spire/contrib/progress/task.py +0 -40
- django_spire/contrib/progress/tracker.py +0 -245
- {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/WHEEL +0 -0
- {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/top_level.txt +0 -0
django_spire/consts.py
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from django_spire.contrib.progress.enums import ProgressStatus
|
|
2
|
-
from django_spire.contrib.progress.
|
|
3
|
-
from django_spire.contrib.progress.
|
|
4
|
-
from django_spire.contrib.progress.states import TaskState, TrackerState
|
|
5
|
-
from django_spire.contrib.progress.task import ProgressMessages, Task, TaskResult
|
|
6
|
-
from django_spire.contrib.progress.tracker import ProgressTracker
|
|
4
|
+
from django_spire.contrib.progress.tasks import ParallelTask, SequentialTask
|
|
5
|
+
from django_spire.contrib.progress.session import ProgressSession
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
__all__ = [
|
|
10
|
-
'
|
|
9
|
+
'ParallelTask',
|
|
10
|
+
'ProgressSession',
|
|
11
11
|
'ProgressStatus',
|
|
12
|
-
'
|
|
13
|
-
'ProgressTrackingMixin',
|
|
14
|
-
'Task',
|
|
15
|
-
'TaskProgressUpdater',
|
|
16
|
-
'TaskResult',
|
|
17
|
-
'TaskState',
|
|
18
|
-
'TrackerState'
|
|
12
|
+
'SequentialTask',
|
|
19
13
|
]
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from django.core.cache import cache
|
|
11
|
+
|
|
12
|
+
from django_spire.contrib.progress.enums import ProgressStatus
|
|
13
|
+
from django_spire.contrib.progress.tasks import ParallelTask, SequentialTask
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from typing_extensions import Any, Callable, Generator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
SIMULATION_MESSAGES = [
|
|
20
|
+
'Initializing...',
|
|
21
|
+
'Processing...',
|
|
22
|
+
'Analyzing...',
|
|
23
|
+
'Generating...',
|
|
24
|
+
'Finalizing...',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ProgressSession:
|
|
29
|
+
_CACHE_PREFIX = 'progress_session:'
|
|
30
|
+
_CACHE_TIMEOUT = 300
|
|
31
|
+
_COMPLETION_INCREMENT = 3.0
|
|
32
|
+
_COMPLETION_INTERVAL = 0.03
|
|
33
|
+
_MAX_SIMULATED_PERCENT = 85
|
|
34
|
+
_SIMULATION_INTERVAL = 0.1
|
|
35
|
+
|
|
36
|
+
def __init__(self, session_id: str, tasks: dict[str, str]) -> None:
|
|
37
|
+
self._lock = threading.Lock()
|
|
38
|
+
self._simulation_threads: dict[str, tuple[threading.Event, threading.Thread]] = {}
|
|
39
|
+
|
|
40
|
+
self._tasks: dict[str, dict[str, Any]] = {
|
|
41
|
+
task_id: {
|
|
42
|
+
'complete_message': '',
|
|
43
|
+
'message': '',
|
|
44
|
+
'name': name,
|
|
45
|
+
'percent': 0.0,
|
|
46
|
+
'status': ProgressStatus.PENDING,
|
|
47
|
+
}
|
|
48
|
+
for task_id, name in tasks.items()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
self.session_id = session_id
|
|
52
|
+
|
|
53
|
+
def _calculate_increment(self, current_percent: float) -> float:
|
|
54
|
+
remaining = self._MAX_SIMULATED_PERCENT - current_percent
|
|
55
|
+
ratio = remaining / self._MAX_SIMULATED_PERCENT
|
|
56
|
+
eased = ratio * ratio
|
|
57
|
+
increment = 0.8 * eased
|
|
58
|
+
|
|
59
|
+
return max(increment, 0.05)
|
|
60
|
+
|
|
61
|
+
def _calculate_message_index(self, percent: float) -> int:
|
|
62
|
+
message_index = int((percent / 100) * len(SIMULATION_MESSAGES))
|
|
63
|
+
|
|
64
|
+
return min(message_index, len(SIMULATION_MESSAGES) - 1)
|
|
65
|
+
|
|
66
|
+
def _delete(self) -> None:
|
|
67
|
+
cache.delete(f'{self._CACHE_PREFIX}{self.session_id}')
|
|
68
|
+
|
|
69
|
+
def _save(self) -> None:
|
|
70
|
+
data = {
|
|
71
|
+
'session_id': self.session_id,
|
|
72
|
+
'tasks': {
|
|
73
|
+
task_id: {
|
|
74
|
+
'complete_message': task['complete_message'],
|
|
75
|
+
'message': task['message'],
|
|
76
|
+
'name': task['name'],
|
|
77
|
+
'percent': task['percent'],
|
|
78
|
+
'status': task['status'].value,
|
|
79
|
+
}
|
|
80
|
+
for task_id, task in self._tasks.items()
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
cache.set(f'{self._CACHE_PREFIX}{self.session_id}', data, timeout=self._CACHE_TIMEOUT)
|
|
85
|
+
|
|
86
|
+
def _simulate_progress(
|
|
87
|
+
self,
|
|
88
|
+
task_id: str,
|
|
89
|
+
stop_event: threading.Event,
|
|
90
|
+
) -> None:
|
|
91
|
+
while not stop_event.is_set():
|
|
92
|
+
with self._lock:
|
|
93
|
+
if task_id not in self._tasks:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
task = self._tasks[task_id]
|
|
97
|
+
|
|
98
|
+
if task['status'] == ProgressStatus.RUNNING:
|
|
99
|
+
self._tick_running(task)
|
|
100
|
+
|
|
101
|
+
if task['status'] == ProgressStatus.COMPLETING:
|
|
102
|
+
if self._tick_completing(task):
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
interval = self._COMPLETION_INTERVAL if task['status'] == ProgressStatus.COMPLETING else self._SIMULATION_INTERVAL
|
|
106
|
+
stop_event.wait(interval)
|
|
107
|
+
|
|
108
|
+
def _tick_completing(self, task: dict[str, Any]) -> bool:
|
|
109
|
+
if task['percent'] < 100.0:
|
|
110
|
+
task['percent'] = min(task['percent'] + self._COMPLETION_INCREMENT, 100.0)
|
|
111
|
+
self._save()
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
task['status'] = ProgressStatus.COMPLETE
|
|
115
|
+
task['message'] = task['complete_message']
|
|
116
|
+
self._save()
|
|
117
|
+
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
def _tick_running(self, task: dict[str, Any]) -> None:
|
|
121
|
+
if task['percent'] >= self._MAX_SIMULATED_PERCENT:
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
increment = self._calculate_increment(task['percent'])
|
|
125
|
+
task['percent'] = min(task['percent'] + increment, self._MAX_SIMULATED_PERCENT)
|
|
126
|
+
task['message'] = SIMULATION_MESSAGES[self._calculate_message_index(task['percent'])]
|
|
127
|
+
|
|
128
|
+
self._save()
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def has_error(self) -> bool:
|
|
132
|
+
tasks = [
|
|
133
|
+
task['status'] == ProgressStatus.ERROR
|
|
134
|
+
for task in self._tasks.values()
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
return any(tasks)
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def is_complete(self) -> bool:
|
|
141
|
+
tasks = [
|
|
142
|
+
task['status'] == ProgressStatus.COMPLETE
|
|
143
|
+
for task in self._tasks.values()
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
return all(tasks)
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def is_running(self) -> bool:
|
|
150
|
+
tasks = [
|
|
151
|
+
task['status'] in (ProgressStatus.RUNNING, ProgressStatus.COMPLETING)
|
|
152
|
+
for task in self._tasks.values()
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
return any(tasks)
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def overall_percent(self) -> int:
|
|
159
|
+
if not self._tasks:
|
|
160
|
+
return 0
|
|
161
|
+
|
|
162
|
+
total_percent = sum(task['percent'] for task in self._tasks.values())
|
|
163
|
+
|
|
164
|
+
return int(total_percent / len(self._tasks))
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def status(self) -> ProgressStatus:
|
|
168
|
+
if self.has_error:
|
|
169
|
+
return ProgressStatus.ERROR
|
|
170
|
+
|
|
171
|
+
if self.is_complete:
|
|
172
|
+
return ProgressStatus.COMPLETE
|
|
173
|
+
|
|
174
|
+
if self.is_running:
|
|
175
|
+
return ProgressStatus.RUNNING
|
|
176
|
+
|
|
177
|
+
return ProgressStatus.PENDING
|
|
178
|
+
|
|
179
|
+
def add_parallel(self, task_id: str, future: Any) -> ParallelTask:
|
|
180
|
+
return ParallelTask(self, task_id, future)
|
|
181
|
+
|
|
182
|
+
def add_sequential(self, task_id: str, func: Callable, *args: Any, **kwargs: Any) -> SequentialTask:
|
|
183
|
+
return SequentialTask(self, task_id, func, *args, **kwargs)
|
|
184
|
+
|
|
185
|
+
def complete(self, task_id: str, message: str | None = None) -> None:
|
|
186
|
+
with self._lock:
|
|
187
|
+
if task_id in self._tasks:
|
|
188
|
+
task = self._tasks[task_id]
|
|
189
|
+
task['status'] = ProgressStatus.COMPLETING
|
|
190
|
+
task['complete_message'] = message or f'{task["name"]} complete'
|
|
191
|
+
task['message'] = 'Completing...'
|
|
192
|
+
self._save()
|
|
193
|
+
|
|
194
|
+
def error(self, task_id: str, message: str | None = None) -> None:
|
|
195
|
+
if task_id in self._simulation_threads:
|
|
196
|
+
stop_event, _ = self._simulation_threads[task_id]
|
|
197
|
+
stop_event.set()
|
|
198
|
+
del self._simulation_threads[task_id]
|
|
199
|
+
|
|
200
|
+
with self._lock:
|
|
201
|
+
if task_id in self._tasks:
|
|
202
|
+
task = self._tasks[task_id]
|
|
203
|
+
task['status'] = ProgressStatus.ERROR
|
|
204
|
+
task['message'] = message or f'{task["name"]} failed'
|
|
205
|
+
self._save()
|
|
206
|
+
|
|
207
|
+
def start(self, task_id: str) -> None:
|
|
208
|
+
with self._lock:
|
|
209
|
+
if task_id in self._tasks:
|
|
210
|
+
task = self._tasks[task_id]
|
|
211
|
+
task['percent'] = 0.0
|
|
212
|
+
task['status'] = ProgressStatus.RUNNING
|
|
213
|
+
task['message'] = SIMULATION_MESSAGES[0]
|
|
214
|
+
self._save()
|
|
215
|
+
|
|
216
|
+
stop_event = threading.Event()
|
|
217
|
+
|
|
218
|
+
thread = threading.Thread(
|
|
219
|
+
target=self._simulate_progress,
|
|
220
|
+
args=(task_id, stop_event),
|
|
221
|
+
daemon=True,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
self._simulation_threads[task_id] = (stop_event, thread)
|
|
225
|
+
thread.start()
|
|
226
|
+
|
|
227
|
+
def stream(self, poll_interval: float = 0.05) -> Generator[str, None, None]:
|
|
228
|
+
while True:
|
|
229
|
+
with self._lock:
|
|
230
|
+
data = self.to_dict()
|
|
231
|
+
|
|
232
|
+
yield f'{json.dumps(data)}\n'
|
|
233
|
+
|
|
234
|
+
if self.is_complete or self.has_error:
|
|
235
|
+
self._delete()
|
|
236
|
+
break
|
|
237
|
+
|
|
238
|
+
time.sleep(poll_interval)
|
|
239
|
+
|
|
240
|
+
def to_dict(self) -> dict[str, Any]:
|
|
241
|
+
return {
|
|
242
|
+
'overall_percent': self.overall_percent,
|
|
243
|
+
'session_id': self.session_id,
|
|
244
|
+
'status': self.status.value,
|
|
245
|
+
'tasks': {
|
|
246
|
+
task_id: {
|
|
247
|
+
'message': task['message'],
|
|
248
|
+
'name': task['name'],
|
|
249
|
+
'percent': int(task['percent']),
|
|
250
|
+
'status': task['status'].value if task['status'] != ProgressStatus.COMPLETING else ProgressStatus.RUNNING.value,
|
|
251
|
+
}
|
|
252
|
+
for task_id, task in self._tasks.items()
|
|
253
|
+
},
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@classmethod
|
|
257
|
+
def create(cls, tasks: dict[str, str]) -> ProgressSession:
|
|
258
|
+
session_id = str(uuid.uuid4())
|
|
259
|
+
session = cls(session_id, tasks)
|
|
260
|
+
session._save()
|
|
261
|
+
return session
|
|
262
|
+
|
|
263
|
+
@classmethod
|
|
264
|
+
def get(cls, session_id: str) -> ProgressSession | None:
|
|
265
|
+
data = cache.get(f'{cls._CACHE_PREFIX}{session_id}')
|
|
266
|
+
|
|
267
|
+
if data is None:
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
session = cls(data['session_id'], {})
|
|
271
|
+
|
|
272
|
+
session._tasks = {
|
|
273
|
+
task_id: {
|
|
274
|
+
'complete_message': task['complete_message'],
|
|
275
|
+
'message': task['message'],
|
|
276
|
+
'name': task['name'],
|
|
277
|
+
'percent': task['percent'],
|
|
278
|
+
'status': ProgressStatus(task['status']),
|
|
279
|
+
}
|
|
280
|
+
for task_id, task in data['tasks'].items()
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return session
|
|
@@ -1,87 +1,65 @@
|
|
|
1
1
|
class ProgressStream {
|
|
2
2
|
constructor(url, config = {}) {
|
|
3
3
|
this.url = url;
|
|
4
|
-
this.is_running = false;
|
|
5
|
-
this.poll_interval = null;
|
|
6
4
|
|
|
7
5
|
this.config = {
|
|
8
|
-
on_update: config.on_update || (() => {}),
|
|
9
6
|
on_complete: config.on_complete || (() => {}),
|
|
10
7
|
on_error: config.on_error || (() => {}),
|
|
11
|
-
|
|
8
|
+
on_update: config.on_update || (() => {}),
|
|
12
9
|
redirect_delay: config.redirect_delay || 1000,
|
|
13
|
-
|
|
10
|
+
redirect_url: config.redirect_url || null,
|
|
14
11
|
};
|
|
15
12
|
}
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
if (this.
|
|
14
|
+
_redirect() {
|
|
15
|
+
if (!this.config.redirect_url) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
setTimeout(() => {
|
|
20
|
+
window.location.href = this.config.redirect_url;
|
|
21
|
+
}, this.config.redirect_delay);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.is_running = false;
|
|
24
|
+
async start() {
|
|
25
|
+
let response = await fetch(this.url);
|
|
28
26
|
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
this.config.on_error({ message: `HTTP ${response.status}` });
|
|
29
|
+
return;
|
|
32
30
|
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
_start_polling() {
|
|
36
|
-
this._poll();
|
|
37
31
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
this._poll();
|
|
41
|
-
}
|
|
42
|
-
}, this.config.poll_interval);
|
|
43
|
-
}
|
|
32
|
+
let reader = response.body.getReader();
|
|
33
|
+
let decoder = new TextDecoder();
|
|
44
34
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let response = await fetch(this.url, {
|
|
48
|
-
method: 'POST',
|
|
49
|
-
headers: {
|
|
50
|
-
'X-CSRFToken': get_cookie('csrftoken'),
|
|
51
|
-
}
|
|
52
|
-
});
|
|
35
|
+
while (true) {
|
|
36
|
+
let { done, value } = await reader.read();
|
|
53
37
|
|
|
54
|
-
if (
|
|
55
|
-
|
|
38
|
+
if (done) {
|
|
39
|
+
break;
|
|
56
40
|
}
|
|
57
41
|
|
|
58
|
-
let
|
|
42
|
+
let chunk = decoder.decode(value, { stream: true });
|
|
43
|
+
let lines = chunk.split('\n').filter(line => line.trim());
|
|
59
44
|
|
|
60
|
-
|
|
45
|
+
for (let line of lines) {
|
|
46
|
+
let data = JSON.parse(line);
|
|
61
47
|
|
|
62
|
-
|
|
63
|
-
this.config.on_error(data);
|
|
64
|
-
this.stop();
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
48
|
+
this.config.on_update(data);
|
|
67
49
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
50
|
+
if (data.status === 'error') {
|
|
51
|
+
this.config.on_error(data);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (data.status === 'complete') {
|
|
56
|
+
this.config.on_complete(data);
|
|
57
|
+
this._redirect();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
72
60
|
}
|
|
73
|
-
} catch (error) {
|
|
74
|
-
console.warn('Poll error:', error.message);
|
|
75
61
|
}
|
|
76
62
|
}
|
|
77
|
-
|
|
78
|
-
_redirect() {
|
|
79
|
-
if (!this.config.redirect_on_complete) return;
|
|
80
|
-
|
|
81
|
-
setTimeout(() => {
|
|
82
|
-
window.location.href = this.config.redirect_on_complete;
|
|
83
|
-
}, this.config.redirect_delay);
|
|
84
|
-
}
|
|
85
63
|
}
|
|
86
64
|
|
|
87
65
|
window.ProgressStream = ProgressStream;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Any, Callable
|
|
9
|
+
|
|
10
|
+
from django_spire.contrib.progress.session import ProgressSession
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ParallelTask:
|
|
14
|
+
def __init__(self, session: ProgressSession, task_id: str, future: Any) -> None:
|
|
15
|
+
self._future = future
|
|
16
|
+
self._session = session
|
|
17
|
+
self._task_id = task_id
|
|
18
|
+
|
|
19
|
+
self._session.start(task_id)
|
|
20
|
+
|
|
21
|
+
thread = threading.Thread(
|
|
22
|
+
target=self._wait_for_completion,
|
|
23
|
+
daemon=True,
|
|
24
|
+
)
|
|
25
|
+
thread.start()
|
|
26
|
+
|
|
27
|
+
def _wait_for_completion(self) -> None:
|
|
28
|
+
try:
|
|
29
|
+
_ = self._future.result
|
|
30
|
+
self._session.complete(self._task_id)
|
|
31
|
+
except Exception:
|
|
32
|
+
self._session.error(self._task_id)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def result(self) -> Any:
|
|
36
|
+
return self._future.result
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SequentialTask:
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
session: ProgressSession,
|
|
43
|
+
task_id: str,
|
|
44
|
+
func: Callable,
|
|
45
|
+
*args: Any,
|
|
46
|
+
**kwargs: Any,
|
|
47
|
+
) -> None:
|
|
48
|
+
self._exception: Exception | None = None
|
|
49
|
+
self._result: Any = None
|
|
50
|
+
self._session = session
|
|
51
|
+
self._task_id = task_id
|
|
52
|
+
|
|
53
|
+
self._session.start(task_id)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
self._result = func(*args, **kwargs)
|
|
57
|
+
self._session.complete(task_id)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
self._exception = e
|
|
60
|
+
self._session.error(task_id)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def result(self) -> Any:
|
|
64
|
+
if self._exception is not None:
|
|
65
|
+
raise self._exception
|
|
66
|
+
|
|
67
|
+
return self._result
|
|
@@ -7,35 +7,36 @@
|
|
|
7
7
|
|
|
8
8
|
{% block card_title_content %}
|
|
9
9
|
<div
|
|
10
|
-
data-stream-url="{% block progress_stream_url %}{% endblock %}"
|
|
11
|
-
data-redirect-url="{% block progress_redirect_url %}{% endblock %}"
|
|
12
10
|
data-redirect-delay="{% block progress_redirect_delay %}1000{% endblock %}"
|
|
11
|
+
data-redirect-url="{% block progress_redirect_url %}{% endblock %}"
|
|
12
|
+
data-stream-url="{% block progress_stream_url %}{% endblock %}"
|
|
13
13
|
x-data="{
|
|
14
|
-
step: 'starting',
|
|
15
|
-
message: 'Initializing...',
|
|
16
|
-
progress: 0,
|
|
17
14
|
has_error: false,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const stream_url = this.$el.dataset.streamUrl;
|
|
21
|
-
const redirect_url = this.$el.dataset.redirectUrl;
|
|
22
|
-
const redirect_delay = parseInt(this.$el.dataset.redirectDelay);
|
|
15
|
+
message: 'Initializing...',
|
|
16
|
+
overall_percent: 0,
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.message = data.message;
|
|
28
|
-
this.progress = data.progress;
|
|
29
|
-
},
|
|
30
|
-
on_complete: (data) => {},
|
|
18
|
+
async init() {
|
|
19
|
+
let stream = new ProgressStream(this.$el.dataset.streamUrl, {
|
|
20
|
+
on_complete: () => {},
|
|
31
21
|
on_error: (data) => {
|
|
32
22
|
this.has_error = true;
|
|
33
|
-
this.message = data.message;
|
|
23
|
+
this.message = data.message || 'An error occurred';
|
|
24
|
+
},
|
|
25
|
+
on_update: (data) => {
|
|
26
|
+
this.overall_percent = data.overall_percent;
|
|
27
|
+
|
|
28
|
+
let tasks = Object.values(data.tasks);
|
|
29
|
+
let running_task = tasks.find(t => t.status === 'running');
|
|
30
|
+
|
|
31
|
+
if (running_task) {
|
|
32
|
+
this.message = running_task.message;
|
|
33
|
+
}
|
|
34
34
|
},
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
redirect_delay: parseInt(this.$el.dataset.redirectDelay),
|
|
36
|
+
redirect_url: this.$el.dataset.redirectUrl,
|
|
37
37
|
});
|
|
38
|
-
|
|
38
|
+
|
|
39
|
+
await stream.start();
|
|
39
40
|
}
|
|
40
41
|
}"
|
|
41
42
|
>
|
|
@@ -43,14 +44,14 @@
|
|
|
43
44
|
<div class="col-12" x-show="!has_error">
|
|
44
45
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
45
46
|
<span class="fs-6" x-text="message"></span>
|
|
46
|
-
{% include 'django_spire/badge/accent_badge.html' with x_badge_text="
|
|
47
|
+
{% include 'django_spire/badge/accent_badge.html' with x_badge_text="overall_percent + '%'" %}
|
|
47
48
|
</div>
|
|
48
49
|
<div class="progress" style="height: 8px;">
|
|
49
50
|
<div
|
|
50
51
|
class="progress-bar bg-app-accent"
|
|
51
52
|
role="progressbar"
|
|
52
|
-
:style="'width: ' +
|
|
53
|
-
:aria-valuenow="
|
|
53
|
+
:style="'width: ' + overall_percent + '%'"
|
|
54
|
+
:aria-valuenow="overall_percent"
|
|
54
55
|
aria-valuemin="0"
|
|
55
56
|
aria-valuemax="100"
|
|
56
57
|
></div>
|