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/blueprints/helpers.py
CHANGED
|
@@ -7,11 +7,38 @@ import re
|
|
|
7
7
|
import sys
|
|
8
8
|
import shutil
|
|
9
9
|
import subprocess
|
|
10
|
+
import threading
|
|
10
11
|
from datetime import datetime
|
|
11
12
|
from flask import current_app
|
|
12
13
|
|
|
13
14
|
from qalita.internal.utils import logger
|
|
14
15
|
|
|
16
|
+
# Lock for .current_env file operations
|
|
17
|
+
_current_env_lock = threading.Lock()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def write_selected_env_atomic(pointer_path: str, env_path: str) -> bool:
|
|
21
|
+
"""Atomically write the selected env pointer file. Uses locking to prevent race conditions.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
pointer_path: Path to the .current_env pointer file
|
|
25
|
+
env_path: Path to the env file to write in the pointer
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
True if successful, False otherwise
|
|
29
|
+
"""
|
|
30
|
+
with _current_env_lock:
|
|
31
|
+
try:
|
|
32
|
+
# Use atomic write: write to temp file then rename
|
|
33
|
+
temp_path = pointer_path + ".tmp"
|
|
34
|
+
with open(temp_path, "w", encoding="utf-8") as pf:
|
|
35
|
+
pf.write(env_path)
|
|
36
|
+
os.replace(temp_path, pointer_path)
|
|
37
|
+
return True
|
|
38
|
+
except Exception as exc:
|
|
39
|
+
logger.warning(f"Failed to write selected env pointer: {exc}")
|
|
40
|
+
return False
|
|
41
|
+
|
|
15
42
|
|
|
16
43
|
def qalita_home() -> str:
|
|
17
44
|
try:
|
|
@@ -44,30 +71,58 @@ def parse_env_file(env_path: str) -> dict:
|
|
|
44
71
|
|
|
45
72
|
|
|
46
73
|
def materialize_env_from_process_env(target_path: str) -> None:
|
|
74
|
+
"""Update specific keys in an env file from process environment variables.
|
|
75
|
+
|
|
76
|
+
This function preserves ALL existing keys in the file and only updates
|
|
77
|
+
specific keys (NAME, MODE, TOKEN, URL) from the process environment.
|
|
78
|
+
It should only be called when creating a NEW file, not on existing files
|
|
79
|
+
that may contain important configuration.
|
|
80
|
+
"""
|
|
47
81
|
try:
|
|
48
82
|
existing: dict[str, str] = {}
|
|
49
83
|
if os.path.isfile(target_path):
|
|
50
84
|
existing = parse_env_file(target_path) or {}
|
|
51
85
|
env = os.environ
|
|
52
86
|
key_groups = [
|
|
53
|
-
("
|
|
54
|
-
("
|
|
55
|
-
("
|
|
87
|
+
("QALITA_WORKER_NAME", ["QALITA_WORKER_NAME", "QALITA_AGENT_NAME", "AGENT_NAME", "NAME"]),
|
|
88
|
+
("QALITA_WORKER_MODE", ["QALITA_WORKER_MODE", "QALITA_AGENT_MODE", "AGENT_MODE", "MODE"]),
|
|
89
|
+
("QALITA_WORKER_TOKEN", ["QALITA_WORKER_TOKEN", "QALITA_AGENT_TOKEN", "QALITA_TOKEN", "TOKEN"]),
|
|
56
90
|
(
|
|
57
|
-
"
|
|
58
|
-
["QALITA_AGENT_ENDPOINT", "AGENT_ENDPOINT", "QALITA_URL", "URL"],
|
|
91
|
+
"QALITA_WORKER_ENDPOINT",
|
|
92
|
+
["QALITA_WORKER_ENDPOINT", "QALITA_AGENT_ENDPOINT", "AGENT_ENDPOINT", "QALITA_URL", "URL"],
|
|
59
93
|
),
|
|
60
94
|
]
|
|
61
|
-
|
|
62
|
-
for _, aliases in key_groups:
|
|
63
|
-
value = None
|
|
64
|
-
for k in aliases:
|
|
65
|
-
if k in env and env.get(k):
|
|
66
|
-
value = env.get(k)
|
|
67
|
-
updates[k] = value # type: ignore[assignment]
|
|
68
|
-
break
|
|
95
|
+
# Start with existing keys to preserve everything
|
|
69
96
|
merged = dict(existing)
|
|
70
|
-
|
|
97
|
+
|
|
98
|
+
# Only update specific keys from environment if they exist
|
|
99
|
+
# Map environment variable names to their canonical key names (use WORKER as canonical)
|
|
100
|
+
canonical_keys = {
|
|
101
|
+
"QALITA_WORKER_NAME": "QALITA_WORKER_NAME",
|
|
102
|
+
"QALITA_AGENT_NAME": "QALITA_WORKER_NAME",
|
|
103
|
+
"AGENT_NAME": "QALITA_WORKER_NAME",
|
|
104
|
+
"NAME": "QALITA_WORKER_NAME",
|
|
105
|
+
"QALITA_WORKER_MODE": "QALITA_WORKER_MODE",
|
|
106
|
+
"QALITA_AGENT_MODE": "QALITA_WORKER_MODE",
|
|
107
|
+
"AGENT_MODE": "QALITA_WORKER_MODE",
|
|
108
|
+
"MODE": "QALITA_WORKER_MODE",
|
|
109
|
+
"QALITA_WORKER_TOKEN": "QALITA_WORKER_TOKEN",
|
|
110
|
+
"QALITA_AGENT_TOKEN": "QALITA_WORKER_TOKEN",
|
|
111
|
+
"QALITA_TOKEN": "QALITA_WORKER_TOKEN",
|
|
112
|
+
"TOKEN": "QALITA_WORKER_TOKEN",
|
|
113
|
+
"QALITA_WORKER_ENDPOINT": "QALITA_WORKER_ENDPOINT",
|
|
114
|
+
"QALITA_AGENT_ENDPOINT": "QALITA_WORKER_ENDPOINT",
|
|
115
|
+
"AGENT_ENDPOINT": "QALITA_WORKER_ENDPOINT",
|
|
116
|
+
"QALITA_URL": "QALITA_WORKER_ENDPOINT",
|
|
117
|
+
"URL": "QALITA_WORKER_ENDPOINT",
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Update only the canonical keys from environment
|
|
121
|
+
for env_key, canonical_key in canonical_keys.items():
|
|
122
|
+
if env_key in env and env.get(env_key):
|
|
123
|
+
merged[canonical_key] = env.get(env_key)
|
|
124
|
+
|
|
125
|
+
# Write all keys (existing + updated)
|
|
71
126
|
lines = []
|
|
72
127
|
for k in sorted(merged.keys()):
|
|
73
128
|
v = merged[k]
|
|
@@ -87,6 +142,17 @@ def materialize_env_from_process_env(target_path: str) -> None:
|
|
|
87
142
|
|
|
88
143
|
|
|
89
144
|
def ensure_default_env_selected(pointer_path: str):
|
|
145
|
+
"""Ensure a default env is selected. Uses locking to prevent race conditions."""
|
|
146
|
+
# Double-check: if file was created by another thread, use it
|
|
147
|
+
try:
|
|
148
|
+
if os.path.isfile(pointer_path):
|
|
149
|
+
with open(pointer_path, "r", encoding="utf-8") as f:
|
|
150
|
+
existing_path = f.read().strip()
|
|
151
|
+
if existing_path and os.path.isfile(existing_path):
|
|
152
|
+
return existing_path
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
|
155
|
+
|
|
90
156
|
try:
|
|
91
157
|
base = qalita_home()
|
|
92
158
|
env = os.environ
|
|
@@ -103,65 +169,80 @@ def ensure_default_env_selected(pointer_path: str):
|
|
|
103
169
|
target = os.path.normpath(os.path.join(base, f".env-{safe}"))
|
|
104
170
|
os.makedirs(base, exist_ok=True)
|
|
105
171
|
materialize_env_from_process_env(target)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
pf.write(target)
|
|
109
|
-
except Exception:
|
|
110
|
-
pass
|
|
172
|
+
# Use atomic write function (which handles locking)
|
|
173
|
+
write_selected_env_atomic(pointer_path, target)
|
|
111
174
|
return target
|
|
112
175
|
except Exception:
|
|
113
176
|
return None
|
|
114
177
|
|
|
115
178
|
|
|
116
179
|
def read_selected_env():
|
|
180
|
+
"""Read the selected env file path. Uses locking to prevent race conditions when creating default.
|
|
181
|
+
|
|
182
|
+
NOTE: Does NOT call materialize_env_from_process_env() to avoid overwriting existing .env files.
|
|
183
|
+
The materialization should only happen when explicitly creating a new file, not on every read.
|
|
184
|
+
"""
|
|
117
185
|
p = selected_env_file_path()
|
|
118
186
|
try:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
187
|
+
# Try to read the file first (read-only, no lock needed)
|
|
188
|
+
if os.path.isfile(p):
|
|
189
|
+
with open(p, "r", encoding="utf-8") as f:
|
|
190
|
+
raw = f.read().strip()
|
|
191
|
+
path = os.path.normpath(raw) if raw else None
|
|
192
|
+
if path and os.path.isfile(path):
|
|
193
|
+
# DO NOT call materialize_env_from_process_env() here - it would overwrite the file!
|
|
194
|
+
# Only return the path without modifying the file
|
|
195
|
+
return path
|
|
196
|
+
# Path exists but file doesn't - try to find it
|
|
123
197
|
try:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
try:
|
|
129
|
-
base = qalita_home()
|
|
130
|
-
if path:
|
|
131
|
-
candidate = os.path.normpath(
|
|
132
|
-
os.path.join(base, os.path.basename(path))
|
|
133
|
-
)
|
|
134
|
-
if os.path.isfile(candidate):
|
|
135
|
-
logger.warning(
|
|
136
|
-
f"Selected env pointer [{path}] not found. Using [{candidate}] under current QALITA_HOME."
|
|
198
|
+
base = qalita_home()
|
|
199
|
+
if path:
|
|
200
|
+
candidate = os.path.normpath(
|
|
201
|
+
os.path.join(base, os.path.basename(path))
|
|
137
202
|
)
|
|
203
|
+
if os.path.isfile(candidate):
|
|
204
|
+
logger.warning(
|
|
205
|
+
f"Selected env pointer [{path}] not found. Using [{candidate}] under current QALITA_HOME."
|
|
206
|
+
)
|
|
207
|
+
# DO NOT call materialize_env_from_process_env() - preserve existing file content
|
|
208
|
+
# Use atomic write function (which handles locking)
|
|
209
|
+
# Double-check: if file was updated by another thread, use it
|
|
210
|
+
try:
|
|
211
|
+
if os.path.isfile(p):
|
|
212
|
+
with open(p, "r", encoding="utf-8") as f2:
|
|
213
|
+
existing = f2.read().strip()
|
|
214
|
+
if existing and os.path.isfile(existing):
|
|
215
|
+
return existing
|
|
216
|
+
except Exception:
|
|
217
|
+
pass
|
|
218
|
+
write_selected_env_atomic(p, candidate)
|
|
219
|
+
return candidate
|
|
138
220
|
try:
|
|
221
|
+
os.makedirs(base, exist_ok=True)
|
|
222
|
+
# Only call materialize_env_from_process_env when creating a NEW file
|
|
223
|
+
# This is safe because the file doesn't exist yet
|
|
139
224
|
materialize_env_from_process_env(candidate)
|
|
225
|
+
logger.warning(
|
|
226
|
+
f"Selected env pointer [{path}] not found. Created [{candidate}] under current QALITA_HOME from environment."
|
|
227
|
+
)
|
|
228
|
+
# Use atomic write function (which handles locking)
|
|
229
|
+
# Double-check: if file was updated by another thread, use it
|
|
230
|
+
try:
|
|
231
|
+
if os.path.isfile(p):
|
|
232
|
+
with open(p, "r", encoding="utf-8") as f2:
|
|
233
|
+
existing = f2.read().strip()
|
|
234
|
+
if existing and os.path.isfile(existing):
|
|
235
|
+
return existing
|
|
236
|
+
except Exception:
|
|
237
|
+
pass
|
|
238
|
+
write_selected_env_atomic(p, candidate)
|
|
239
|
+
return candidate
|
|
140
240
|
except Exception:
|
|
141
241
|
pass
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
pass
|
|
147
|
-
return candidate
|
|
148
|
-
try:
|
|
149
|
-
os.makedirs(base, exist_ok=True)
|
|
150
|
-
materialize_env_from_process_env(candidate)
|
|
151
|
-
logger.warning(
|
|
152
|
-
f"Selected env pointer [{path}] not found. Created [{candidate}] under current QALITA_HOME from environment."
|
|
153
|
-
)
|
|
154
|
-
try:
|
|
155
|
-
with open(p, "w", encoding="utf-8") as pf:
|
|
156
|
-
pf.write(candidate)
|
|
157
|
-
except Exception:
|
|
158
|
-
pass
|
|
159
|
-
return candidate
|
|
160
|
-
except Exception:
|
|
161
|
-
pass
|
|
162
|
-
except Exception:
|
|
163
|
-
pass
|
|
164
|
-
return ensure_default_env_selected(p)
|
|
242
|
+
except Exception:
|
|
243
|
+
pass
|
|
244
|
+
# File doesn't exist or is invalid - create default (with lock)
|
|
245
|
+
return ensure_default_env_selected(p)
|
|
165
246
|
except Exception:
|
|
166
247
|
logger.warning(f"No selected env pointer found at [{p}] or failed to read it")
|
|
167
248
|
return ensure_default_env_selected(p)
|
|
@@ -283,7 +364,7 @@ def compute_agent_summary(cfg):
|
|
|
283
364
|
|
|
284
365
|
agent_conf = {
|
|
285
366
|
"name": pick(raw, "context", "remote", "name") or raw.get("name", ""),
|
|
286
|
-
"mode": raw.get("mode", ""),
|
|
367
|
+
"mode": pick(raw, "context", "local", "mode") or raw.get("mode", ""),
|
|
287
368
|
"url": pick(raw, "context", "local", "url") or raw.get("url", ""),
|
|
288
369
|
"agent_id": pick(raw, "context", "remote", "id")
|
|
289
370
|
or raw.get("agent_id", ""),
|
|
@@ -303,19 +384,22 @@ def compute_agent_summary(cfg):
|
|
|
303
384
|
if agent_conf is None:
|
|
304
385
|
agent_conf = {}
|
|
305
386
|
agent_conf["name"] = (
|
|
306
|
-
data.get("
|
|
387
|
+
data.get("QALITA_WORKER_NAME")
|
|
388
|
+
or data.get("QALITA_AGENT_NAME")
|
|
307
389
|
or data.get("AGENT_NAME")
|
|
308
390
|
or data.get("NAME")
|
|
309
391
|
or agent_conf.get("name", "")
|
|
310
392
|
)
|
|
311
393
|
agent_conf["mode"] = (
|
|
312
|
-
data.get("
|
|
394
|
+
data.get("QALITA_WORKER_MODE")
|
|
395
|
+
or data.get("QALITA_AGENT_MODE")
|
|
313
396
|
or data.get("AGENT_MODE")
|
|
314
397
|
or data.get("MODE")
|
|
315
398
|
or agent_conf.get("mode", "")
|
|
316
399
|
)
|
|
317
400
|
agent_conf["url"] = (
|
|
318
|
-
data.get("
|
|
401
|
+
data.get("QALITA_WORKER_ENDPOINT")
|
|
402
|
+
or data.get("QALITA_AGENT_ENDPOINT")
|
|
319
403
|
or data.get("QALITA_URL")
|
|
320
404
|
or data.get("URL")
|
|
321
405
|
or agent_conf.get("url", "")
|
|
@@ -362,3 +446,10 @@ def compute_agent_summary(cfg):
|
|
|
362
446
|
except Exception:
|
|
363
447
|
agent_runs = []
|
|
364
448
|
return agent_conf, agent_runs
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
# Aliases for backward compatibility (agent -> worker refactoring)
|
|
452
|
+
worker_pid_file_path = agent_pid_file_path
|
|
453
|
+
worker_log_file_path = agent_log_file_path
|
|
454
|
+
worker_status_payload = agent_status_payload
|
|
455
|
+
compute_worker_summary = compute_agent_summary
|
qalita/web/blueprints/sources.py
CHANGED
|
@@ -13,10 +13,7 @@ from qalita.internal.utils import logger
|
|
|
13
13
|
from flask import (
|
|
14
14
|
Blueprint,
|
|
15
15
|
current_app,
|
|
16
|
-
redirect,
|
|
17
|
-
render_template,
|
|
18
16
|
request,
|
|
19
|
-
url_for,
|
|
20
17
|
jsonify,
|
|
21
18
|
)
|
|
22
19
|
from qalita.internal.request import send_request
|
|
@@ -88,37 +85,10 @@ def list_sources():
|
|
|
88
85
|
if isinstance(platform_url, str):
|
|
89
86
|
platform_url = platform_url.rstrip("/")
|
|
90
87
|
|
|
91
|
-
return
|
|
92
|
-
"sources
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
@bp.get("/add")
|
|
97
|
-
def add_source_view():
|
|
98
|
-
# If a valid type is provided as query param, go directly to the form with that type preselected.
|
|
99
|
-
# Otherwise, render the selection grid first.
|
|
100
|
-
t = (request.args.get("type") or "").strip().lower()
|
|
101
|
-
allowed = [
|
|
102
|
-
"file",
|
|
103
|
-
"csv",
|
|
104
|
-
"excel",
|
|
105
|
-
"folder",
|
|
106
|
-
"postgresql",
|
|
107
|
-
"mysql",
|
|
108
|
-
"oracle",
|
|
109
|
-
"mssql",
|
|
110
|
-
"sqlite",
|
|
111
|
-
"mongodb",
|
|
112
|
-
"s3",
|
|
113
|
-
"gcs",
|
|
114
|
-
"azure_blob",
|
|
115
|
-
"hdfs",
|
|
116
|
-
]
|
|
117
|
-
if t and t in allowed:
|
|
118
|
-
return render_template(
|
|
119
|
-
"sources/edit.html", title="Add source", source=None, preselected_type=t
|
|
120
|
-
)
|
|
121
|
-
return render_template("sources/select-source.html")
|
|
88
|
+
return jsonify({
|
|
89
|
+
"sources": sources,
|
|
90
|
+
"platform_url": platform_url,
|
|
91
|
+
})
|
|
122
92
|
|
|
123
93
|
|
|
124
94
|
@bp.post("/add")
|
|
@@ -245,12 +215,10 @@ def add_source_post():
|
|
|
245
215
|
}
|
|
246
216
|
|
|
247
217
|
if not validate_source_object(cfg, new_source, skip_connection=False):
|
|
248
|
-
return
|
|
249
|
-
"
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
error="Validation failed. Check fields and connectivity.",
|
|
253
|
-
)
|
|
218
|
+
return jsonify({
|
|
219
|
+
"ok": False,
|
|
220
|
+
"error": "Validation failed. Check fields and connectivity.",
|
|
221
|
+
}), 400
|
|
254
222
|
|
|
255
223
|
cfg.config.setdefault("sources", []).append(new_source)
|
|
256
224
|
cfg.save_source_config()
|
|
@@ -329,19 +297,13 @@ def add_source_post():
|
|
|
329
297
|
except Exception:
|
|
330
298
|
src_id = None
|
|
331
299
|
|
|
332
|
-
return
|
|
333
|
-
"
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
cfg = current_app.config["QALITA_CONFIG_OBJ"]
|
|
340
|
-
cfg.load_source_config()
|
|
341
|
-
src = next(
|
|
342
|
-
(s for s in cfg.config.get("sources", []) if s.get("name") == name), None
|
|
343
|
-
)
|
|
344
|
-
return render_template("sources/edit.html", title="Edit source", source=src)
|
|
300
|
+
return jsonify({
|
|
301
|
+
"ok": True,
|
|
302
|
+
"name": name,
|
|
303
|
+
"platform_url": platform_url,
|
|
304
|
+
"source_id": src_id,
|
|
305
|
+
"message": f"Source '{name}' added successfully.",
|
|
306
|
+
})
|
|
345
307
|
|
|
346
308
|
|
|
347
309
|
@bp.post("/edit/<name>")
|
|
@@ -482,12 +444,10 @@ def edit_source_post(name):
|
|
|
482
444
|
if not validate_source_object(
|
|
483
445
|
cfg, updated, skip_connection=False, exclude_name=name
|
|
484
446
|
):
|
|
485
|
-
return
|
|
486
|
-
"
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
error="Validation failed. Check fields and connectivity.",
|
|
490
|
-
)
|
|
447
|
+
return jsonify({
|
|
448
|
+
"ok": False,
|
|
449
|
+
"error": "Validation failed. Check fields and connectivity.",
|
|
450
|
+
}), 400
|
|
491
451
|
|
|
492
452
|
sources[i].update(updated)
|
|
493
453
|
break
|
|
@@ -504,7 +464,11 @@ def edit_source_post(name):
|
|
|
504
464
|
push_single_programmatic(cfg, new_name, approve_public=False)
|
|
505
465
|
except (SystemExit, Exception):
|
|
506
466
|
pass
|
|
507
|
-
return
|
|
467
|
+
return jsonify({
|
|
468
|
+
"ok": True,
|
|
469
|
+
"name": new_name,
|
|
470
|
+
"message": f"Source '{new_name}' updated successfully.",
|
|
471
|
+
})
|
|
508
472
|
|
|
509
473
|
|
|
510
474
|
@bp.post("/delete/<name>")
|
|
@@ -515,7 +479,11 @@ def delete_source_post(name):
|
|
|
515
479
|
s for s in cfg.config.get("sources", []) if s.get("name") != name
|
|
516
480
|
]
|
|
517
481
|
cfg.save_source_config()
|
|
518
|
-
return
|
|
482
|
+
return jsonify({
|
|
483
|
+
"ok": True,
|
|
484
|
+
"name": name,
|
|
485
|
+
"message": f"Source '{name}' deleted successfully.",
|
|
486
|
+
})
|
|
519
487
|
|
|
520
488
|
|
|
521
489
|
@bp.get("/pick-file")
|