qalita 2.3.1__py3-none-any.whl → 2.5.2__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.
- qalita/__main__.py +213 -9
- qalita/commands/{agent.py → worker.py} +89 -89
- qalita/internal/config.py +26 -19
- qalita/internal/utils.py +1 -1
- qalita/web/app.py +97 -14
- qalita/web/blueprints/context.py +13 -60
- qalita/web/blueprints/dashboard.py +35 -76
- qalita/web/blueprints/helpers.py +154 -63
- qalita/web/blueprints/sources.py +29 -61
- qalita/web/blueprints/{agents.py → workers.py} +108 -185
- qalita-2.5.2.dist-info/METADATA +66 -0
- qalita-2.5.2.dist-info/RECORD +24 -0
- {qalita-2.3.1.dist-info → qalita-2.5.2.dist-info}/WHEEL +1 -1
- qalita-2.5.2.dist-info/entry_points.txt +2 -0
- qalita/web/blueprints/studio.py +0 -1255
- qalita/web/public/chatgpt.svg +0 -3
- qalita/web/public/claude.png +0 -0
- qalita/web/public/favicon.ico +0 -0
- qalita/web/public/gemini.png +0 -0
- qalita/web/public/logo-no-slogan.png +0 -0
- qalita/web/public/logo-white-no-slogan.svg +0 -11
- qalita/web/public/mistral.svg +0 -1
- qalita/web/public/noise.webp +0 -0
- qalita/web/public/ollama.png +0 -0
- qalita/web/public/platform.png +0 -0
- qalita/web/public/sources-logos/alloy-db.png +0 -0
- qalita/web/public/sources-logos/amazon-athena.png +0 -0
- qalita/web/public/sources-logos/amazon-rds.png +0 -0
- qalita/web/public/sources-logos/api.svg +0 -2
- qalita/web/public/sources-logos/avro.svg +0 -20
- qalita/web/public/sources-logos/azure-database-mysql.png +0 -0
- qalita/web/public/sources-logos/azure-database-postgresql.png +0 -0
- qalita/web/public/sources-logos/azure-sql-database.png +0 -0
- qalita/web/public/sources-logos/azure-sql-managed-instance.png +0 -0
- qalita/web/public/sources-logos/azure-synapse-analytics.png +0 -0
- qalita/web/public/sources-logos/azure_blob.svg +0 -1
- qalita/web/public/sources-logos/bigquery.png +0 -0
- qalita/web/public/sources-logos/cassandra.svg +0 -254
- qalita/web/public/sources-logos/clickhouse.png +0 -0
- qalita/web/public/sources-logos/cloud-sql.png +0 -0
- qalita/web/public/sources-logos/cockroach-db.png +0 -0
- qalita/web/public/sources-logos/csv.svg +0 -1
- qalita/web/public/sources-logos/database.svg +0 -3
- qalita/web/public/sources-logos/databricks.png +0 -0
- qalita/web/public/sources-logos/duckdb.png +0 -0
- qalita/web/public/sources-logos/elasticsearch.svg +0 -1
- qalita/web/public/sources-logos/excel.svg +0 -1
- qalita/web/public/sources-logos/file.svg +0 -1
- qalita/web/public/sources-logos/folder.svg +0 -6
- qalita/web/public/sources-logos/gcs.png +0 -0
- qalita/web/public/sources-logos/hdfs.svg +0 -1
- qalita/web/public/sources-logos/ibm-db2.png +0 -0
- qalita/web/public/sources-logos/json.png +0 -0
- qalita/web/public/sources-logos/maria-db.png +0 -0
- qalita/web/public/sources-logos/mongodb.svg +0 -1
- qalita/web/public/sources-logos/mssql.svg +0 -1
- qalita/web/public/sources-logos/mysql.svg +0 -7
- qalita/web/public/sources-logos/oracle.svg +0 -4
- qalita/web/public/sources-logos/parquet.svg +0 -16
- qalita/web/public/sources-logos/picture.png +0 -0
- qalita/web/public/sources-logos/postgresql.svg +0 -22
- qalita/web/public/sources-logos/questdb.png +0 -0
- qalita/web/public/sources-logos/redshift.png +0 -0
- qalita/web/public/sources-logos/s3.svg +0 -34
- qalita/web/public/sources-logos/sap-hana.png +0 -0
- qalita/web/public/sources-logos/sftp.png +0 -0
- qalita/web/public/sources-logos/single-store.png +0 -0
- qalita/web/public/sources-logos/snowflake.png +0 -0
- qalita/web/public/sources-logos/sqlite.svg +0 -104
- qalita/web/public/sources-logos/sqlserver.png +0 -0
- qalita/web/public/sources-logos/starburst.png +0 -0
- qalita/web/public/sources-logos/stream.png +0 -0
- qalita/web/public/sources-logos/teradata.png +0 -0
- qalita/web/public/sources-logos/timescale.png +0 -0
- qalita/web/public/sources-logos/xls.svg +0 -1
- qalita/web/public/sources-logos/xlsx.svg +0 -1
- qalita/web/public/sources-logos/yugabyte-db.png +0 -0
- qalita/web/public/studio-logo.svg +0 -10
- qalita/web/public/studio.css +0 -304
- qalita/web/public/studio.png +0 -0
- qalita/web/public/styles.css +0 -682
- qalita/web/templates/dashboard.html +0 -373
- qalita/web/templates/navbar.html +0 -40
- qalita/web/templates/sources/added.html +0 -57
- qalita/web/templates/sources/edit.html +0 -411
- qalita/web/templates/sources/select-source.html +0 -128
- qalita/web/templates/studio/agent-panel.html +0 -769
- qalita/web/templates/studio/context-panel.html +0 -300
- qalita/web/templates/studio/index.html +0 -79
- qalita/web/templates/studio/navbar.html +0 -14
- qalita/web/templates/studio/view-panel.html +0 -529
- qalita-2.3.1.dist-info/METADATA +0 -58
- qalita-2.3.1.dist-info/RECORD +0 -101
- qalita-2.3.1.dist-info/entry_points.txt +0 -3
- {qalita-2.3.1.dist-info → qalita-2.5.2.dist-info}/licenses/LICENSE +0 -0
qalita/web/app.py
CHANGED
|
@@ -3,38 +3,121 @@
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
-
from flask import Flask
|
|
6
|
+
from flask import Flask, request
|
|
7
|
+
from flask_socketio import SocketIO
|
|
7
8
|
from waitress import serve
|
|
8
9
|
|
|
10
|
+
# Global SocketIO instance
|
|
11
|
+
socketio = None
|
|
12
|
+
|
|
9
13
|
def create_app(config_obj) -> Flask:
|
|
10
|
-
"""Application factory for the QALITA CLI UI."""
|
|
11
|
-
|
|
12
|
-
__name__,
|
|
13
|
-
static_folder=os.path.join(os.path.dirname(__file__), "public"),
|
|
14
|
-
static_url_path="/static",
|
|
15
|
-
template_folder=os.path.join(os.path.dirname(__file__), "templates"),
|
|
16
|
-
)
|
|
14
|
+
"""Application factory for the QALITA CLI UI - API only."""
|
|
15
|
+
global socketio
|
|
17
16
|
|
|
17
|
+
app = Flask(__name__)
|
|
18
18
|
app.config["QALITA_CONFIG_OBJ"] = config_obj
|
|
19
|
+
app.config["SECRET_KEY"] = os.urandom(24)
|
|
20
|
+
|
|
21
|
+
# Initialize SocketIO with CORS enabled
|
|
22
|
+
# Try gevent first, fall back to threading if gevent causes issues (e.g., Python 3.13 compatibility)
|
|
23
|
+
async_mode = "threading" # Default to threading for better compatibility
|
|
24
|
+
try:
|
|
25
|
+
# Try to use gevent if available and working
|
|
26
|
+
import sys
|
|
27
|
+
if sys.version_info < (3, 13):
|
|
28
|
+
# Only try gevent on Python < 3.13 due to zope.interface compatibility issues
|
|
29
|
+
async_mode = "gevent"
|
|
30
|
+
except Exception:
|
|
31
|
+
pass
|
|
19
32
|
|
|
20
|
-
|
|
33
|
+
socketio = SocketIO(
|
|
34
|
+
app,
|
|
35
|
+
cors_allowed_origins="*",
|
|
36
|
+
async_mode=async_mode,
|
|
37
|
+
logger=False,
|
|
38
|
+
engineio_logger=False,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Register blueprints (API only, no templates)
|
|
21
42
|
from qalita.web.blueprints.dashboard import bp as dashboard_bp
|
|
22
43
|
from qalita.web.blueprints.context import bp as context_bp
|
|
23
|
-
from qalita.web.blueprints.agents import bp as agents_bp
|
|
24
44
|
from qalita.web.blueprints.sources import bp as sources_bp
|
|
25
|
-
from qalita.web.blueprints.
|
|
45
|
+
from qalita.web.blueprints.workers import bp as workers_bp
|
|
26
46
|
|
|
27
47
|
app.register_blueprint(dashboard_bp)
|
|
28
48
|
app.register_blueprint(context_bp)
|
|
29
|
-
app.register_blueprint(agents_bp)
|
|
30
49
|
app.register_blueprint(sources_bp, url_prefix="/sources")
|
|
31
|
-
app.register_blueprint(
|
|
50
|
+
app.register_blueprint(workers_bp) # No prefix - routes already include /worker/ and /agent/
|
|
51
|
+
|
|
52
|
+
# Try to import qalita-studio if installed (optional dependency)
|
|
53
|
+
try:
|
|
54
|
+
from qalita_studio.api.blueprints.studio import bp as studio_bp
|
|
55
|
+
from qalita_studio.api.blueprints.studio import register_studio_socketio_handlers
|
|
56
|
+
app.register_blueprint(studio_bp, url_prefix="/studio")
|
|
57
|
+
register_studio_socketio_handlers()
|
|
58
|
+
except ImportError:
|
|
59
|
+
pass # qalita-studio not installed
|
|
60
|
+
|
|
61
|
+
# CORS headers for Next.js frontend
|
|
62
|
+
# IMPORTANT: Socket.IO requests must be completely bypassed from Flask middleware
|
|
63
|
+
# as they don't follow standard WSGI flow and can cause "write() before start_response" errors
|
|
64
|
+
@app.after_request
|
|
65
|
+
def after_request(response):
|
|
66
|
+
# Skip Socket.IO requests - they are handled by Socket.IO middleware and don't follow standard WSGI flow
|
|
67
|
+
# We need to detect Socket.IO requests very early and return immediately to avoid WSGI protocol violations
|
|
68
|
+
try:
|
|
69
|
+
# Check WSGI environment PATH_INFO directly - this is the most reliable method
|
|
70
|
+
# and works even if Flask's request context is in an invalid state
|
|
71
|
+
environ = None
|
|
72
|
+
if hasattr(request, 'environ'):
|
|
73
|
+
environ = request.environ
|
|
74
|
+
elif hasattr(request, '_get_current_object'):
|
|
75
|
+
# Try to get the underlying request object
|
|
76
|
+
try:
|
|
77
|
+
req_obj = request._get_current_object()
|
|
78
|
+
if hasattr(req_obj, 'environ'):
|
|
79
|
+
environ = req_obj.environ
|
|
80
|
+
except (AttributeError, RuntimeError):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
if environ:
|
|
84
|
+
path_info = environ.get('PATH_INFO', '')
|
|
85
|
+
# If this is a Socket.IO request, return immediately without any processing
|
|
86
|
+
if path_info and path_info.startswith('/socket.io/'):
|
|
87
|
+
return response
|
|
88
|
+
except (AttributeError, RuntimeError, KeyError, TypeError):
|
|
89
|
+
# If we can't safely check, return response as-is to prevent WSGI violations
|
|
90
|
+
return response
|
|
91
|
+
|
|
92
|
+
# Only add CORS headers for non-Socket.IO requests
|
|
93
|
+
# Also check that response object is valid before modifying
|
|
94
|
+
try:
|
|
95
|
+
if response and hasattr(response, 'headers'):
|
|
96
|
+
response.headers.add('Access-Control-Allow-Origin', '*')
|
|
97
|
+
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
|
|
98
|
+
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
|
|
99
|
+
except (AttributeError, RuntimeError):
|
|
100
|
+
# If response object is invalid, just return it as-is
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
return response
|
|
32
104
|
|
|
33
105
|
return app
|
|
34
106
|
|
|
35
107
|
|
|
108
|
+
def get_socketio():
|
|
109
|
+
"""Get the global SocketIO instance."""
|
|
110
|
+
return socketio
|
|
111
|
+
|
|
112
|
+
|
|
36
113
|
def run_dashboard_ui(config_obj, host: str = "localhost", port: int = 7070):
|
|
37
114
|
app = create_app(config_obj)
|
|
115
|
+
socketio = get_socketio()
|
|
38
116
|
url = f"http://{host}:{port}"
|
|
39
117
|
print(f"QALITA CLI UI is running. Open {url}")
|
|
40
|
-
|
|
118
|
+
# Use SocketIO's built-in server for WebSocket support
|
|
119
|
+
if socketio:
|
|
120
|
+
socketio.run(app, host=host, port=port, use_reloader=False, log_output=False, allow_unsafe_werkzeug=True)
|
|
121
|
+
else:
|
|
122
|
+
from waitress import serve
|
|
123
|
+
serve(app, host=host, port=port)
|
qalita/web/blueprints/context.py
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
from flask import Blueprint, jsonify, current_app, request
|
|
6
6
|
import requests
|
|
7
|
+
import os
|
|
7
8
|
|
|
8
9
|
from .helpers import (
|
|
9
10
|
list_env_files,
|
|
@@ -11,11 +12,12 @@ from .helpers import (
|
|
|
11
12
|
selected_env_file_path,
|
|
12
13
|
qalita_home,
|
|
13
14
|
parse_env_file,
|
|
15
|
+
write_selected_env_atomic,
|
|
14
16
|
)
|
|
15
17
|
from qalita.internal.utils import logger
|
|
16
18
|
from qalita.internal.request import send_request
|
|
17
19
|
from qalita.internal.utils import get_version
|
|
18
|
-
from qalita.commands.
|
|
20
|
+
from qalita.commands.worker import authenticate, send_alive
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
bp = Blueprint("context", __name__)
|
|
@@ -27,8 +29,6 @@ def list_contexts():
|
|
|
27
29
|
selected = read_selected_env()
|
|
28
30
|
try:
|
|
29
31
|
if selected:
|
|
30
|
-
import os
|
|
31
|
-
|
|
32
32
|
sel_norm = os.path.normcase(os.path.normpath(selected))
|
|
33
33
|
for it in files:
|
|
34
34
|
if os.path.normcase(os.path.normpath(it.get("path", ""))) == sel_norm:
|
|
@@ -41,8 +41,6 @@ def list_contexts():
|
|
|
41
41
|
|
|
42
42
|
@bp.post("/context/select")
|
|
43
43
|
def select_context():
|
|
44
|
-
import os
|
|
45
|
-
|
|
46
44
|
data = request.get_json(silent=True) or {}
|
|
47
45
|
path = (data.get("path") or "").strip()
|
|
48
46
|
|
|
@@ -51,8 +49,11 @@ def select_context():
|
|
|
51
49
|
if not path:
|
|
52
50
|
try:
|
|
53
51
|
p = selected_env_file_path()
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
# Use lock when removing to avoid race conditions
|
|
53
|
+
from .helpers import _current_env_lock
|
|
54
|
+
with _current_env_lock:
|
|
55
|
+
if os.path.exists(p):
|
|
56
|
+
os.remove(p)
|
|
56
57
|
ok = True
|
|
57
58
|
message = "Selection cleared"
|
|
58
59
|
except Exception as exc:
|
|
@@ -69,8 +70,11 @@ def select_context():
|
|
|
69
70
|
if not os.path.isfile(abs_path):
|
|
70
71
|
logger.warning("Context env file not found on disk")
|
|
71
72
|
return jsonify({"ok": False, "message": "Env file not found"}), 404
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
|
|
74
|
+
# Use atomic write with locking
|
|
75
|
+
p = selected_env_file_path()
|
|
76
|
+
if not write_selected_env_atomic(p, abs_path):
|
|
77
|
+
return jsonify({"ok": False, "message": "Failed to write context selection"}), 500
|
|
74
78
|
|
|
75
79
|
# Apply selected context: login and persist
|
|
76
80
|
try:
|
|
@@ -145,54 +149,3 @@ def _login_with_env(env_path: str) -> None:
|
|
|
145
149
|
pass
|
|
146
150
|
except Exception:
|
|
147
151
|
pass
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
@bp.get("/context/issues")
|
|
151
|
-
def list_user_issues():
|
|
152
|
-
"""Proxy list of issues for the current user using selected context env.
|
|
153
|
-
|
|
154
|
-
Reads the selected env file (or current app config if already set) to get URL and TOKEN,
|
|
155
|
-
then calls backend /api/v2/issues and returns items.
|
|
156
|
-
"""
|
|
157
|
-
cfg = current_app.config["QALITA_CONFIG_OBJ"]
|
|
158
|
-
url = getattr(cfg, "url", None)
|
|
159
|
-
token = getattr(cfg, "token", None)
|
|
160
|
-
try:
|
|
161
|
-
if not url or not token:
|
|
162
|
-
# Fallback to selected env file
|
|
163
|
-
sel_path = read_selected_env()
|
|
164
|
-
if sel_path:
|
|
165
|
-
data = parse_env_file(sel_path)
|
|
166
|
-
url = (
|
|
167
|
-
data.get("QALITA_AGENT_ENDPOINT")
|
|
168
|
-
or data.get("QALITA_URL")
|
|
169
|
-
or data.get("URL")
|
|
170
|
-
or url
|
|
171
|
-
)
|
|
172
|
-
token = data.get("QALITA_AGENT_TOKEN") or data.get("QALITA_TOKEN") or data.get("TOKEN") or token
|
|
173
|
-
except Exception:
|
|
174
|
-
pass
|
|
175
|
-
if not url or not token:
|
|
176
|
-
return jsonify({"ok": False, "message": "Missing platform URL or TOKEN in context"}), 400
|
|
177
|
-
try:
|
|
178
|
-
base = str(url).rstrip("/")
|
|
179
|
-
endpoint = base + "/api/v2/issues"
|
|
180
|
-
headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}
|
|
181
|
-
r = requests.get(endpoint, headers=headers, timeout=10)
|
|
182
|
-
if 200 <= r.status_code < 300:
|
|
183
|
-
try:
|
|
184
|
-
body = r.json()
|
|
185
|
-
except Exception:
|
|
186
|
-
body = []
|
|
187
|
-
# Normalize items: some APIs return { items: [...] }, others a list directly
|
|
188
|
-
items = body.get("items") if isinstance(body, dict) else body
|
|
189
|
-
if not isinstance(items, list):
|
|
190
|
-
items = []
|
|
191
|
-
return jsonify({"ok": True, "items": items})
|
|
192
|
-
try:
|
|
193
|
-
err = r.json()
|
|
194
|
-
except Exception:
|
|
195
|
-
err = {"detail": r.text[:200]}
|
|
196
|
-
return jsonify({"ok": False, "status": r.status_code, "error": err}), 200
|
|
197
|
-
except Exception as exc:
|
|
198
|
-
return jsonify({"ok": False, "message": str(exc)}), 200
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# QALITA (c) COPYRIGHT 2025 - ALL RIGHTS RESERVED -
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from flask import Blueprint, current_app,
|
|
5
|
+
from flask import Blueprint, current_app, request, jsonify
|
|
6
6
|
|
|
7
7
|
from qalita.internal.request import send_request
|
|
8
8
|
from .helpers import (
|
|
@@ -17,6 +17,20 @@ bp = Blueprint("dashboard", __name__)
|
|
|
17
17
|
|
|
18
18
|
@bp.route("/")
|
|
19
19
|
def dashboard():
|
|
20
|
+
"""Legacy route - redirects to Next.js frontend"""
|
|
21
|
+
# Next.js will handle the frontend
|
|
22
|
+
# This route is kept for backward compatibility but should not be used
|
|
23
|
+
return _get_dashboard_data_json()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@bp.route("/api/dashboard")
|
|
27
|
+
def dashboard_api():
|
|
28
|
+
"""API endpoint for dashboard data"""
|
|
29
|
+
return _get_dashboard_data_json()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _get_dashboard_data_json():
|
|
33
|
+
"""Get dashboard data as JSON"""
|
|
20
34
|
cfg = current_app.config["QALITA_CONFIG_OBJ"]
|
|
21
35
|
agent_conf, agent_runs = compute_agent_summary(cfg)
|
|
22
36
|
# Load local sources regardless of agent configuration
|
|
@@ -81,80 +95,25 @@ def dashboard():
|
|
|
81
95
|
runs_start = (start_idx + 1) if total_runs > 0 and start_idx < total_runs else 0
|
|
82
96
|
runs_end = min(end_idx, total_runs) if total_runs > 0 else 0
|
|
83
97
|
|
|
84
|
-
return
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
)
|
|
98
|
+
return jsonify({
|
|
99
|
+
"agent_conf": agent_conf or {},
|
|
100
|
+
"sources": sources,
|
|
101
|
+
"agent_runs": agent_runs,
|
|
102
|
+
"agent_runs_page": agent_runs_page,
|
|
103
|
+
"runs_total": total_runs,
|
|
104
|
+
"runs_page": page,
|
|
105
|
+
"runs_per_page": per_page,
|
|
106
|
+
"runs_has_prev": runs_has_prev,
|
|
107
|
+
"runs_has_next": runs_has_next,
|
|
108
|
+
"runs_start": runs_start,
|
|
109
|
+
"runs_end": runs_end,
|
|
110
|
+
"platform_url": platform_url,
|
|
111
|
+
})
|
|
99
112
|
|
|
100
113
|
|
|
101
114
|
def dashboard_with_feedback(feedback_msg=None, feedback_level: str = "info"):
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
# Load local sources for display
|
|
105
|
-
try:
|
|
106
|
-
cfg.load_source_config()
|
|
107
|
-
sources = list(reversed(cfg.config.get("sources", [])))
|
|
108
|
-
except Exception:
|
|
109
|
-
sources = []
|
|
110
|
-
# Resolve platform URL as above, best effort
|
|
111
|
-
platform_url = None
|
|
112
|
-
try:
|
|
113
|
-
backend_url = getattr(cfg, "url", None)
|
|
114
|
-
# Try override from selected .env context to ensure consistency after POST actions
|
|
115
|
-
try:
|
|
116
|
-
env_path = read_selected_env()
|
|
117
|
-
if env_path:
|
|
118
|
-
data = parse_env_file(env_path)
|
|
119
|
-
backend_url = (
|
|
120
|
-
data.get("QALITA_AGENT_ENDPOINT")
|
|
121
|
-
or data.get("QALITA_URL")
|
|
122
|
-
or data.get("URL")
|
|
123
|
-
or backend_url
|
|
124
|
-
)
|
|
125
|
-
except Exception:
|
|
126
|
-
pass
|
|
127
|
-
if backend_url:
|
|
128
|
-
try:
|
|
129
|
-
r = send_request.__wrapped__(
|
|
130
|
-
cfg, request=f"{backend_url}/api/v1/info", mode="get"
|
|
131
|
-
) # type: ignore[attr-defined]
|
|
132
|
-
except Exception:
|
|
133
|
-
r = None
|
|
134
|
-
if r is not None and getattr(r, "status_code", None) == 200:
|
|
135
|
-
try:
|
|
136
|
-
platform_url = (r.json() or {}).get("public_platform_url")
|
|
137
|
-
except Exception:
|
|
138
|
-
platform_url = None
|
|
139
|
-
except Exception:
|
|
140
|
-
platform_url = None
|
|
141
|
-
return render_template(
|
|
142
|
-
"dashboard.html",
|
|
143
|
-
agent_conf=agent_conf or {},
|
|
144
|
-
sources=sources,
|
|
145
|
-
agent_runs=agent_runs,
|
|
146
|
-
agent_runs_page=agent_runs[:10],
|
|
147
|
-
runs_total=len(agent_runs),
|
|
148
|
-
runs_page=1,
|
|
149
|
-
runs_per_page=10,
|
|
150
|
-
runs_has_prev=False,
|
|
151
|
-
runs_has_next=len(agent_runs) > 10,
|
|
152
|
-
runs_start=1 if agent_runs else 0,
|
|
153
|
-
runs_end=min(10, len(agent_runs)) if agent_runs else 0,
|
|
154
|
-
feedback=feedback_msg,
|
|
155
|
-
feedback_level=feedback_level,
|
|
156
|
-
platform_url=platform_url,
|
|
157
|
-
)
|
|
115
|
+
"""Legacy function - kept for backward compatibility"""
|
|
116
|
+
return _get_dashboard_data_json()
|
|
158
117
|
|
|
159
118
|
|
|
160
119
|
@bp.post("/validate")
|
|
@@ -188,7 +147,7 @@ def validate_sources():
|
|
|
188
147
|
except Exception:
|
|
189
148
|
msg = "Validation completed."
|
|
190
149
|
level = "info"
|
|
191
|
-
return
|
|
150
|
+
return jsonify({"ok": True, "message": msg, "level": level})
|
|
192
151
|
|
|
193
152
|
|
|
194
153
|
@bp.post("/push")
|
|
@@ -202,7 +161,7 @@ def push_sources():
|
|
|
202
161
|
except Exception as exc:
|
|
203
162
|
ok, message = False, f"Push failed: {exc}"
|
|
204
163
|
level = "info" if ok else "error"
|
|
205
|
-
return
|
|
164
|
+
return jsonify({"ok": ok, "message": message, "level": level})
|
|
206
165
|
|
|
207
166
|
|
|
208
167
|
@bp.post("/pack/push")
|
|
@@ -222,5 +181,5 @@ def push_pack_from_ui():
|
|
|
222
181
|
else:
|
|
223
182
|
feedback = "Please select a pack folder."
|
|
224
183
|
feedback_level = "error"
|
|
225
|
-
#
|
|
226
|
-
return
|
|
184
|
+
# Return JSON response
|
|
185
|
+
return jsonify({"ok": feedback_level == "info", "message": feedback, "level": feedback_level})
|