Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
83 changes: 83 additions & 0 deletions .github/workflows/daily_pytest_slack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Daily Pytest + Slack (IL 01:00)

on:
schedule:
# 01:00 Israel time — 22:00 UTC (summer), 23:00 UTC (winter)
- cron: "0 22 * * *"
- cron: "0 23 * * *"
workflow_dispatch:

jobs:
run_pytests_and_notify:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi

- name: Run pytest (and keep log)
run: |
pytest -q --maxfail=50 --disable-warnings -rA \
--junitxml=pytest-report.xml > pytest.log 2>&1 || true

- name: Parse results
id: results
run: |
python - <<'PY'
import xml.etree.ElementTree as ET
import os
counts = dict(tests=0, failures=0, errors=0, skipped=0)
try:
tree = ET.parse("pytest-report.xml")
root = tree.getroot()
for suite in root.findall(".//testsuite"):
counts["tests"] += int(suite.attrib.get("tests", 0))
counts["failures"] += int(suite.attrib.get("failures", 0))
counts["errors"] += int(suite.attrib.get("errors", 0))
counts["skipped"] += int(suite.attrib.get("skipped", 0))
except Exception as e:
print("Parse error:", e)
counts["passed"] = counts["tests"] - counts["failures"] - counts["errors"] - counts["skipped"]
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
for k,v in counts.items():
f.write(f"{k}={v}\n")
f.write(f"has_failures={'true' if (counts['failures']>0 or counts['errors']>0) else 'false'}\n")
PY

- name: Send Slack notification (if failures)
if: steps.results.outputs.has_failures == 'true'
uses: slackapi/slack-github-action@v1.25.0
with:
payload: |
{
"channel": "#vast",
"username": "GitHub Actions",
"icon_emoji": ":rotating_light:",
"text": "🚨 *Pytest Failures Detected!*\n\nRepository: ${{ github.repository }}\nRun: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>\n\n*Passed:* ${{ steps.results.outputs.passed }} / ${{ steps.results.outputs.tests }}\n*Failed:* ${{ steps.results.outputs.failures }}\n*Errors:* ${{ steps.results.outputs.errors }}\n*Skipped:* ${{ steps.results.outputs.skipped }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

- name: Send Slack success notification
if: steps.results.outputs.has_failures == 'false'
uses: slackapi/slack-github-action@v1.25.0
with:
payload: |
{
"channel": "#vast",
"username": "GitHub Actions",
"icon_emoji": ":white_check_mark:",
"text": "✅ All tests passed successfully!\n\nRepository: ${{ github.repository }}\nRun: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>\n\nTotal tests: ${{ steps.results.outputs.tests }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
16 changes: 13 additions & 3 deletions .github/workflows/soak.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,19 @@ jobs:
'REFRESH_TTL_DAYS=14' \
'DEV_SA_NAME=ci-service' \
> services/db_api_service/.env



- name: Prepare env for plant_stress
run: |
mkdir -p services/plant_stress
cat > services/plant_stress/.env <<'EOF'
ADDR=0.0.0.0
PORT=8001
MINIO_ENDPOINT=minio:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin123
MINIO_BUCKET=audio
MINIO_PREFIX=samples/
WINDOW_MIN=5
EOF

- name: Start core stack
run: docker compose up -d kafka mosquitto connect
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ __pycache__/
*.pytest_cache/
.venv/
venv/
.coverage

# --- VSCode / Editor ---
.vscode/
Expand All @@ -35,5 +36,4 @@ venv/

# --- OS files ---
.DS_Store
Thumbs.db

Thumbs.db
31 changes: 25 additions & 6 deletions GUI/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
PyQt6==6.9.1
PyQt6-WebEngine==6.9.0

# Web/API
PyQt6==6.7.1
PyQt6-WebEngine==6.7.0
python-vlc


# ───── Web/API ─────
fastapi>=0.110
uvicorn[standard]>=0.29
flask
# Metrics & HTTP

# ───── Metrics & HTTP ─────
prometheus-client>=0.20
requests>=2.31
httpx==0.27.0 # only needed when you switch to real Flink REST
# gRPC & Protobuf

# ───── gRPC & Protobuf ─────
grpcio>=1.56,<2
grpcio-tools>=1.56,<2
protobuf>=6,<7
# Validation / crypto

# ───── Validation / crypto / auth ─────
pydantic>=2.9,<3
argon2-cffi
PyJWT>=2.9.0

# ───── Geospatial / Math ─────
shapely

# ───── Async / misc ─────
aiohttp
plotly
shapely
PyJWT>=2.9.0
sip


57 changes: 57 additions & 0 deletions GUI/src/vast/alerts/alert_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

from PyQt6.QtCore import QObject, pyqtSignal, QUrl, QTimer
from PyQt6.QtWebSockets import QWebSocket
from PyQt6.QtNetwork import QAbstractSocket # ✅ add this
import json


class AlertClient(QObject):
"""
Connects to the alerts WebSocket gateway and emits signals
when new alerts or snapshots arrive.
"""
snapshotReceived = pyqtSignal(list)
alertReceived = pyqtSignal(dict)
connectionLost = pyqtSignal()

def __init__(self, ws_url: str, parent=None):
super().__init__(parent)
self.url = QUrl(ws_url)
self.socket = QWebSocket()
self.socket.connected.connect(self._on_connected)
self.socket.disconnected.connect(self._on_disconnected)
self.socket.textMessageReceived.connect(self._on_message)
self.reconnect_timer = QTimer()
self.reconnect_timer.timeout.connect(self._try_reconnect)
self.reconnect_interval_ms = 5000 # retry every 5s
self._connect()

def _connect(self):
print(f"[AlertClient] Connecting to {self.url.toString()}")
self.socket.open(self.url)

def _try_reconnect(self):
# ✅ Use QAbstractSocket.SocketState instead of QWebSocket.SocketState
if self.socket.state() == QAbstractSocket.SocketState.ConnectedState:
self.reconnect_timer.stop()
return
print("[AlertClient] Attempting reconnect...")
self._connect()


def _on_connected(self):
print("[AlertClient] Connected to alerts gateway.")
self.reconnect_timer.stop()

def _on_disconnected(self):
print("[AlertClient] Disconnected from alerts gateway.")
self.connectionLost.emit()
self.reconnect_timer.start(self.reconnect_interval_ms)

def _on_message(self, msg: str):
try:
payload = json.loads(msg)
if payload["type"] == "alert":
self.alertReceived.emit(payload["data"])
except Exception as e:
print("[AlertClient] Invalid message:", e, msg)
Loading