Compare commits
4 Commits
feat/runne
...
7a4ed3550a
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a4ed3550a | |||
| c88dd19672 | |||
|
|
dc64a66f0f | ||
|
|
3d38e4b4c3 |
@@ -12,4 +12,14 @@
|
||||
<subdirectory>./</subdirectory>
|
||||
</workbench>
|
||||
</content>
|
||||
|
||||
<!-- Kindred Create extensions -->
|
||||
<kindred>
|
||||
<min_create_version>0.1.0</min_create_version>
|
||||
<load_priority>60</load_priority>
|
||||
<pure_python>true</pure_python>
|
||||
<contexts>
|
||||
<context id="*" action="overlay"/>
|
||||
</contexts>
|
||||
</kindred>
|
||||
</package>
|
||||
|
||||
@@ -2338,6 +2338,18 @@ class SiloEventListener(QtCore.QThread):
|
||||
) # (status, retry_count, error_message)
|
||||
server_mode_changed = QtCore.Signal(str) # "normal" / "read-only" / "degraded"
|
||||
|
||||
# DAG events
|
||||
dag_updated = QtCore.Signal(str, int, int) # part_number, node_count, edge_count
|
||||
dag_validated = QtCore.Signal(str, bool, int) # part_number, valid, failed_count
|
||||
|
||||
# Job lifecycle events
|
||||
job_created = QtCore.Signal(str, str, str) # job_id, definition_name, part_number
|
||||
job_claimed = QtCore.Signal(str, str) # job_id, runner_id
|
||||
job_progress = QtCore.Signal(str, int, str) # job_id, progress, message
|
||||
job_completed = QtCore.Signal(str) # job_id
|
||||
job_failed = QtCore.Signal(str, str) # job_id, error
|
||||
job_cancelled = QtCore.Signal(str) # job_id
|
||||
|
||||
_MAX_RETRIES = 10
|
||||
_BASE_DELAY = 1 # seconds, doubles each retry
|
||||
_MAX_DELAY = 60 # seconds, backoff cap
|
||||
@@ -2454,6 +2466,35 @@ class SiloEventListener(QtCore.QThread):
|
||||
self.server_mode_changed.emit(mode)
|
||||
return
|
||||
|
||||
# Job lifecycle events (keyed by job_id, not part_number)
|
||||
job_id = payload.get("job_id", "")
|
||||
if event_type == "job.created":
|
||||
self.job_created.emit(
|
||||
job_id,
|
||||
payload.get("definition_name", ""),
|
||||
payload.get("part_number", ""),
|
||||
)
|
||||
return
|
||||
if event_type == "job.claimed":
|
||||
self.job_claimed.emit(job_id, payload.get("runner_id", ""))
|
||||
return
|
||||
if event_type == "job.progress":
|
||||
self.job_progress.emit(
|
||||
job_id,
|
||||
int(payload.get("progress", 0)),
|
||||
payload.get("message", ""),
|
||||
)
|
||||
return
|
||||
if event_type == "job.completed":
|
||||
self.job_completed.emit(job_id)
|
||||
return
|
||||
if event_type == "job.failed":
|
||||
self.job_failed.emit(job_id, payload.get("error", ""))
|
||||
return
|
||||
if event_type == "job.cancelled":
|
||||
self.job_cancelled.emit(job_id)
|
||||
return
|
||||
|
||||
pn = payload.get("part_number", "")
|
||||
if not pn:
|
||||
return
|
||||
@@ -2463,6 +2504,18 @@ class SiloEventListener(QtCore.QThread):
|
||||
elif event_type == "revision_created":
|
||||
rev = payload.get("revision", 0)
|
||||
self.revision_created.emit(pn, int(rev))
|
||||
elif event_type == "dag.updated":
|
||||
self.dag_updated.emit(
|
||||
pn,
|
||||
int(payload.get("node_count", 0)),
|
||||
int(payload.get("edge_count", 0)),
|
||||
)
|
||||
elif event_type == "dag.validated":
|
||||
self.dag_validated.emit(
|
||||
pn,
|
||||
bool(payload.get("valid", False)),
|
||||
int(payload.get("failed_count", 0)),
|
||||
)
|
||||
|
||||
|
||||
class _SSEUnsupported(Exception):
|
||||
@@ -2679,6 +2732,11 @@ class SiloAuthDockWidget:
|
||||
self._event_listener.revision_created.connect(self._on_remote_revision)
|
||||
self._event_listener.connection_status.connect(self._on_sse_status)
|
||||
self._event_listener.server_mode_changed.connect(self._on_server_mode)
|
||||
self._event_listener.dag_updated.connect(self._on_dag_updated)
|
||||
self._event_listener.dag_validated.connect(self._on_dag_validated)
|
||||
self._event_listener.job_created.connect(self._on_job_created)
|
||||
self._event_listener.job_completed.connect(self._on_job_completed)
|
||||
self._event_listener.job_failed.connect(self._on_job_failed)
|
||||
self._event_listener.start()
|
||||
else:
|
||||
if self._event_listener is not None and self._event_listener.isRunning():
|
||||
@@ -2758,6 +2816,70 @@ class SiloAuthDockWidget:
|
||||
)
|
||||
self._refresh_activity_panel()
|
||||
|
||||
def _on_dag_updated(self, part_number, node_count, edge_count):
|
||||
FreeCAD.Console.PrintMessage(
|
||||
f"Silo: DAG updated for {part_number}"
|
||||
f" ({node_count} nodes, {edge_count} edges)\n"
|
||||
)
|
||||
self._add_activity_entry(
|
||||
f"\u25b6 {part_number} \u2013 DAG synced"
|
||||
f" ({node_count} nodes, {edge_count} edges)",
|
||||
part_number,
|
||||
)
|
||||
|
||||
def _on_dag_validated(self, part_number, valid, failed_count):
|
||||
if valid:
|
||||
status = "\u2713 PASS"
|
||||
FreeCAD.Console.PrintMessage(f"Silo: Validation passed for {part_number}\n")
|
||||
else:
|
||||
status = f"\u2717 FAIL ({failed_count} failed)"
|
||||
FreeCAD.Console.PrintWarning(
|
||||
f"Silo: Validation failed for {part_number}"
|
||||
f" ({failed_count} features failed)\n"
|
||||
)
|
||||
self._add_activity_entry(f"{status} \u2013 {part_number}", part_number)
|
||||
|
||||
def _on_job_created(self, job_id, definition_name, part_number):
|
||||
FreeCAD.Console.PrintMessage(
|
||||
f"Silo: Job {definition_name} created for {part_number}\n"
|
||||
)
|
||||
self._add_activity_entry(
|
||||
f"\u23f3 {part_number} \u2013 {definition_name} queued",
|
||||
part_number,
|
||||
)
|
||||
|
||||
def _on_job_completed(self, job_id):
|
||||
FreeCAD.Console.PrintMessage(f"Silo: Job {job_id} completed\n")
|
||||
self._refresh_activity_panel()
|
||||
|
||||
def _on_job_failed(self, job_id, error):
|
||||
FreeCAD.Console.PrintError(f"Silo: Job {job_id} failed: {error}\n")
|
||||
self._add_activity_entry(f"\u2717 Job {job_id[:8]} failed: {error}", None)
|
||||
|
||||
def _add_activity_entry(self, text, part_number):
|
||||
"""Insert a live event entry at the top of the Activity panel."""
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
|
||||
mw = FreeCADGui.getMainWindow()
|
||||
if mw is None:
|
||||
return
|
||||
panel = mw.findChild(QtWidgets.QDockWidget, "SiloDatabaseActivity")
|
||||
if panel is None:
|
||||
return
|
||||
activity_list = panel.findChild(QtWidgets.QListWidget)
|
||||
if activity_list is None:
|
||||
return
|
||||
|
||||
item = QtWidgets.QListWidgetItem(text)
|
||||
if part_number:
|
||||
item.setData(QtCore.Qt.UserRole, part_number)
|
||||
item.setForeground(QtGui.QColor("#89b4fa"))
|
||||
activity_list.insertItem(0, item)
|
||||
|
||||
# Cap the list at 50 entries
|
||||
while activity_list.count() > 50:
|
||||
activity_list.takeItem(activity_list.count() - 1)
|
||||
|
||||
def _refresh_activity_panel(self):
|
||||
"""Refresh the Database Activity panel if it exists."""
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
|
||||
Submodule silo-client updated: fb658c5a24...5276ff26fa
Reference in New Issue
Block a user