supervaizer 0.9.7__py3-none-any.whl → 0.10.0__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.
- supervaizer/__init__.py +11 -2
- supervaizer/__version__.py +1 -1
- supervaizer/account.py +4 -0
- supervaizer/account_service.py +7 -1
- supervaizer/admin/routes.py +46 -7
- supervaizer/admin/static/js/job-start-form.js +373 -0
- supervaizer/admin/templates/agents.html +74 -0
- supervaizer/admin/templates/agents_grid.html +5 -3
- supervaizer/admin/templates/job_start_test.html +109 -0
- supervaizer/admin/templates/navigation.html +11 -1
- supervaizer/admin/templates/supervaize_instructions.html +212 -0
- supervaizer/agent.py +165 -25
- supervaizer/case.py +46 -14
- supervaizer/cli.py +248 -8
- supervaizer/common.py +45 -4
- supervaizer/deploy/__init__.py +16 -0
- supervaizer/deploy/cli.py +296 -0
- supervaizer/deploy/commands/__init__.py +9 -0
- supervaizer/deploy/commands/clean.py +294 -0
- supervaizer/deploy/commands/down.py +119 -0
- supervaizer/deploy/commands/local.py +460 -0
- supervaizer/deploy/commands/plan.py +167 -0
- supervaizer/deploy/commands/status.py +169 -0
- supervaizer/deploy/commands/up.py +281 -0
- supervaizer/deploy/docker.py +370 -0
- supervaizer/deploy/driver_factory.py +42 -0
- supervaizer/deploy/drivers/__init__.py +39 -0
- supervaizer/deploy/drivers/aws_app_runner.py +607 -0
- supervaizer/deploy/drivers/base.py +196 -0
- supervaizer/deploy/drivers/cloud_run.py +570 -0
- supervaizer/deploy/drivers/do_app_platform.py +504 -0
- supervaizer/deploy/health.py +404 -0
- supervaizer/deploy/state.py +210 -0
- supervaizer/deploy/templates/Dockerfile.template +44 -0
- supervaizer/deploy/templates/debug_env.py +69 -0
- supervaizer/deploy/templates/docker-compose.yml.template +37 -0
- supervaizer/deploy/templates/dockerignore.template +66 -0
- supervaizer/deploy/templates/entrypoint.sh +20 -0
- supervaizer/deploy/utils.py +41 -0
- supervaizer/examples/{controller-template.py → controller_template.py} +5 -4
- supervaizer/job.py +18 -5
- supervaizer/job_service.py +6 -5
- supervaizer/parameter.py +61 -1
- supervaizer/protocol/__init__.py +2 -2
- supervaizer/protocol/a2a/routes.py +1 -1
- supervaizer/routes.py +262 -12
- supervaizer/server.py +5 -11
- supervaizer/utils/__init__.py +16 -0
- supervaizer/utils/version_check.py +56 -0
- {supervaizer-0.9.7.dist-info → supervaizer-0.10.0.dist-info}/METADATA +105 -34
- supervaizer-0.10.0.dist-info/RECORD +76 -0
- {supervaizer-0.9.7.dist-info → supervaizer-0.10.0.dist-info}/WHEEL +1 -1
- supervaizer/protocol/acp/__init__.py +0 -21
- supervaizer/protocol/acp/model.py +0 -198
- supervaizer/protocol/acp/routes.py +0 -74
- supervaizer-0.9.7.dist-info/RECORD +0 -50
- {supervaizer-0.9.7.dist-info → supervaizer-0.10.0.dist-info}/entry_points.txt +0 -0
- {supervaizer-0.9.7.dist-info → supervaizer-0.10.0.dist-info}/licenses/LICENSE.md +0 -0
supervaizer/__init__.py
CHANGED
|
@@ -14,7 +14,14 @@ from supervaizer.agent import (
|
|
|
14
14
|
AgentMethodParams,
|
|
15
15
|
AgentMethods,
|
|
16
16
|
)
|
|
17
|
-
from supervaizer.case import
|
|
17
|
+
from supervaizer.case import (
|
|
18
|
+
Case,
|
|
19
|
+
CaseNodeUpdate,
|
|
20
|
+
CaseNodeType,
|
|
21
|
+
Cases,
|
|
22
|
+
CaseNode,
|
|
23
|
+
CaseNodes,
|
|
24
|
+
)
|
|
18
25
|
from supervaizer.common import ApiError, ApiResult, ApiSuccess
|
|
19
26
|
from supervaizer.event import (
|
|
20
27
|
AgentRegisterEvent,
|
|
@@ -51,8 +58,10 @@ __all__ = [
|
|
|
51
58
|
"ApiSuccess",
|
|
52
59
|
"Case",
|
|
53
60
|
"CaseNodeUpdate",
|
|
54
|
-
"
|
|
61
|
+
"CaseNodeType",
|
|
55
62
|
"Cases",
|
|
63
|
+
"CaseNode",
|
|
64
|
+
"CaseNodes",
|
|
56
65
|
"CaseStartEvent",
|
|
57
66
|
"CaseUpdateEvent",
|
|
58
67
|
"create_error_response",
|
supervaizer/__version__.py
CHANGED
supervaizer/account.py
CHANGED
|
@@ -20,6 +20,7 @@ if TYPE_CHECKING:
|
|
|
20
20
|
from supervaizer.event import Event
|
|
21
21
|
from supervaizer.job import Job
|
|
22
22
|
from supervaizer.server import Server
|
|
23
|
+
from supervaizer.common import log
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class AccountAbstract(SvBaseModel):
|
|
@@ -268,6 +269,9 @@ class Account(AccountAbstract):
|
|
|
268
269
|
|
|
269
270
|
def send_update_case(self, case: "Case", update: "CaseNodeUpdate") -> ApiResult:
|
|
270
271
|
# Import here to avoid circular imports
|
|
272
|
+
log.debug(f"[send_update_case] CaseRef {case} with update {update}")
|
|
273
|
+
log.debug(f"[send_update_case] {type(case)}")
|
|
274
|
+
log.debug(f"[send_update_case] {type(update)}")
|
|
271
275
|
from supervaizer.event import CaseUpdateEvent
|
|
272
276
|
|
|
273
277
|
event = CaseUpdateEvent(case=case, update=update, account=self)
|
supervaizer/account_service.py
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
# https://mozilla.org/MPL/2.0/.
|
|
10
10
|
|
|
11
11
|
import logging
|
|
12
|
+
import os
|
|
12
13
|
from typing import TYPE_CHECKING, Union
|
|
13
14
|
|
|
14
15
|
import httpx
|
|
@@ -19,6 +20,11 @@ logger = logging.getLogger("httpx")
|
|
|
19
20
|
# Enable httpx debug logging (optional - uncomment for transport-level debugging)
|
|
20
21
|
logger.setLevel(logging.DEBUG)
|
|
21
22
|
|
|
23
|
+
_httpx_transport = httpx.HTTPTransport(
|
|
24
|
+
retries=int(os.getenv("SUPERVAIZE_HTTP_MAX_RETRIES", 2))
|
|
25
|
+
)
|
|
26
|
+
_httpx_client = httpx.Client(transport=_httpx_transport)
|
|
27
|
+
|
|
22
28
|
if TYPE_CHECKING:
|
|
23
29
|
from supervaizer.account import Account
|
|
24
30
|
from supervaizer.agent import Agent
|
|
@@ -60,7 +66,7 @@ def send_event(
|
|
|
60
66
|
curl_cmd = f"curl -X 'GET' '{account.url_event}' {curl_headers}"
|
|
61
67
|
|
|
62
68
|
try:
|
|
63
|
-
response =
|
|
69
|
+
response = _httpx_client.post(account.url_event, headers=headers, json=payload)
|
|
64
70
|
|
|
65
71
|
# Log response details before raising for status
|
|
66
72
|
|
supervaizer/admin/routes.py
CHANGED
|
@@ -27,7 +27,7 @@ from fastapi.templating import Jinja2Templates
|
|
|
27
27
|
from pydantic import BaseModel
|
|
28
28
|
from sse_starlette.sse import EventSourceResponse
|
|
29
29
|
|
|
30
|
-
from supervaizer.__version__ import API_VERSION
|
|
30
|
+
from supervaizer.__version__ import API_VERSION, VERSION
|
|
31
31
|
from supervaizer.common import log
|
|
32
32
|
from supervaizer.lifecycle import EntityStatus
|
|
33
33
|
from supervaizer.storage import (
|
|
@@ -257,10 +257,11 @@ def create_admin_routes() -> APIRouter:
|
|
|
257
257
|
stats = get_dashboard_stats(storage)
|
|
258
258
|
|
|
259
259
|
return templates.TemplateResponse(
|
|
260
|
+
request,
|
|
260
261
|
"dashboard.html",
|
|
261
262
|
{
|
|
262
263
|
"request": request,
|
|
263
|
-
"api_version":
|
|
264
|
+
"api_version": VERSION,
|
|
264
265
|
"stats": stats,
|
|
265
266
|
"system_status": "Online",
|
|
266
267
|
"db_name": "TinyDB",
|
|
@@ -276,10 +277,11 @@ def create_admin_routes() -> APIRouter:
|
|
|
276
277
|
async def admin_jobs_page(request: Request) -> Response:
|
|
277
278
|
"""Jobs management page."""
|
|
278
279
|
return templates.TemplateResponse(
|
|
280
|
+
request,
|
|
279
281
|
"jobs_list.html",
|
|
280
282
|
{
|
|
281
283
|
"request": request,
|
|
282
|
-
"api_version":
|
|
284
|
+
"api_version": VERSION,
|
|
283
285
|
"api_key": os.getenv("SUPERVAIZER_API_KEY"),
|
|
284
286
|
},
|
|
285
287
|
)
|
|
@@ -288,10 +290,11 @@ def create_admin_routes() -> APIRouter:
|
|
|
288
290
|
async def admin_cases_page(request: Request) -> Response:
|
|
289
291
|
"""Cases management page."""
|
|
290
292
|
return templates.TemplateResponse(
|
|
293
|
+
request,
|
|
291
294
|
"cases_list.html",
|
|
292
295
|
{
|
|
293
296
|
"request": request,
|
|
294
|
-
"api_version":
|
|
297
|
+
"api_version": VERSION,
|
|
295
298
|
"api_key": os.getenv("SUPERVAIZER_API_KEY"),
|
|
296
299
|
},
|
|
297
300
|
)
|
|
@@ -305,10 +308,11 @@ def create_admin_routes() -> APIRouter:
|
|
|
305
308
|
server_config = get_server_configuration(storage)
|
|
306
309
|
|
|
307
310
|
return templates.TemplateResponse(
|
|
311
|
+
request,
|
|
308
312
|
"server.html",
|
|
309
313
|
{
|
|
310
314
|
"request": request,
|
|
311
|
-
"api_version":
|
|
315
|
+
"api_version": VERSION,
|
|
312
316
|
"server_status": server_status,
|
|
313
317
|
"server_config": server_config,
|
|
314
318
|
"api_key": os.getenv("SUPERVAIZER_API_KEY"),
|
|
@@ -331,10 +335,11 @@ def create_admin_routes() -> APIRouter:
|
|
|
331
335
|
)
|
|
332
336
|
|
|
333
337
|
return templates.TemplateResponse(
|
|
338
|
+
request,
|
|
334
339
|
"agents.html",
|
|
335
340
|
{
|
|
336
341
|
"request": request,
|
|
337
|
-
"api_version":
|
|
342
|
+
"api_version": VERSION,
|
|
338
343
|
"agents": server_info.agents,
|
|
339
344
|
"api_key": os.getenv("SUPERVAIZER_API_KEY"),
|
|
340
345
|
},
|
|
@@ -345,6 +350,30 @@ def create_admin_routes() -> APIRouter:
|
|
|
345
350
|
status_code=503, detail="Server information unavailable"
|
|
346
351
|
) from e
|
|
347
352
|
|
|
353
|
+
@router.get("/job-start-test", response_class=HTMLResponse)
|
|
354
|
+
async def admin_job_start_test_page(request: Request) -> Response:
|
|
355
|
+
"""Job start form test page."""
|
|
356
|
+
return templates.TemplateResponse(
|
|
357
|
+
request,
|
|
358
|
+
"job_start_test.html",
|
|
359
|
+
{
|
|
360
|
+
"request": request,
|
|
361
|
+
"api_version": VERSION,
|
|
362
|
+
"api_key": os.getenv("SUPERVAIZER_API_KEY"),
|
|
363
|
+
},
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
@router.get("/static/js/job-start-form.js")
|
|
367
|
+
async def serve_job_start_form_js() -> Response:
|
|
368
|
+
"""Serve the JobStartForm JavaScript file."""
|
|
369
|
+
js_file_path = Path(__file__).parent / "static" / "js" / "job-start-form.js"
|
|
370
|
+
if js_file_path.exists():
|
|
371
|
+
with open(js_file_path, "r") as f:
|
|
372
|
+
content = f.read()
|
|
373
|
+
return Response(content=content, media_type="application/javascript")
|
|
374
|
+
else:
|
|
375
|
+
raise HTTPException(status_code=404, detail="JavaScript file not found")
|
|
376
|
+
|
|
348
377
|
@router.get("/console", response_class=HTMLResponse)
|
|
349
378
|
async def admin_console_page(request: Request) -> Response:
|
|
350
379
|
"""Interactive console page - publicly accessible, authentication handled by frontend."""
|
|
@@ -355,7 +384,9 @@ def create_admin_routes() -> APIRouter:
|
|
|
355
384
|
console_token = generate_console_token()
|
|
356
385
|
|
|
357
386
|
return templates.TemplateResponse(
|
|
358
|
-
|
|
387
|
+
request,
|
|
388
|
+
"console.html",
|
|
389
|
+
{"request": request, "console_token": console_token},
|
|
359
390
|
)
|
|
360
391
|
|
|
361
392
|
# API Routes
|
|
@@ -371,6 +402,7 @@ def create_admin_routes() -> APIRouter:
|
|
|
371
402
|
server_status = get_server_status()
|
|
372
403
|
|
|
373
404
|
return templates.TemplateResponse(
|
|
405
|
+
request,
|
|
374
406
|
"server_status_cards.html",
|
|
375
407
|
{
|
|
376
408
|
"request": request,
|
|
@@ -443,6 +475,7 @@ def create_admin_routes() -> APIRouter:
|
|
|
443
475
|
pass
|
|
444
476
|
|
|
445
477
|
return templates.TemplateResponse(
|
|
478
|
+
request,
|
|
446
479
|
"agents_grid.html",
|
|
447
480
|
{
|
|
448
481
|
"request": request,
|
|
@@ -480,6 +513,7 @@ def create_admin_routes() -> APIRouter:
|
|
|
480
513
|
raise HTTPException(status_code=404, detail="Agent not found")
|
|
481
514
|
|
|
482
515
|
return templates.TemplateResponse(
|
|
516
|
+
request,
|
|
483
517
|
"agent_detail.html",
|
|
484
518
|
{
|
|
485
519
|
"request": request,
|
|
@@ -563,6 +597,7 @@ def create_admin_routes() -> APIRouter:
|
|
|
563
597
|
jobs.append(job)
|
|
564
598
|
|
|
565
599
|
return templates.TemplateResponse(
|
|
600
|
+
request,
|
|
566
601
|
"jobs_table.html",
|
|
567
602
|
{
|
|
568
603
|
"request": request,
|
|
@@ -591,6 +626,7 @@ def create_admin_routes() -> APIRouter:
|
|
|
591
626
|
cases_data = storage.get_cases_for_job(job_id)
|
|
592
627
|
|
|
593
628
|
return templates.TemplateResponse(
|
|
629
|
+
request,
|
|
594
630
|
"job_detail.html",
|
|
595
631
|
{
|
|
596
632
|
"request": request,
|
|
@@ -681,6 +717,7 @@ def create_admin_routes() -> APIRouter:
|
|
|
681
717
|
cases.append(case)
|
|
682
718
|
|
|
683
719
|
return templates.TemplateResponse(
|
|
720
|
+
request,
|
|
684
721
|
"cases_table.html",
|
|
685
722
|
{
|
|
686
723
|
"request": request,
|
|
@@ -711,6 +748,7 @@ def create_admin_routes() -> APIRouter:
|
|
|
711
748
|
job_data = storage.get_object_by_id("Job", case_data["job_id"])
|
|
712
749
|
|
|
713
750
|
return templates.TemplateResponse(
|
|
751
|
+
request,
|
|
714
752
|
"case_detail.html",
|
|
715
753
|
{
|
|
716
754
|
"request": request,
|
|
@@ -862,6 +900,7 @@ def create_admin_routes() -> APIRouter:
|
|
|
862
900
|
activities = activities[:10] # Top 10 recent activities
|
|
863
901
|
|
|
864
902
|
return templates.TemplateResponse(
|
|
903
|
+
request,
|
|
865
904
|
"recent_activity.html",
|
|
866
905
|
{
|
|
867
906
|
"request": request,
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JobStartForm - Handles job parameter validation and submission
|
|
3
|
+
*
|
|
4
|
+
* This class provides methods to validate agent parameters and method fields
|
|
5
|
+
* before starting a job, using the new validation endpoints.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class JobStartForm {
|
|
9
|
+
constructor(agentPath) {
|
|
10
|
+
this.agentPath = agentPath;
|
|
11
|
+
this.form = null;
|
|
12
|
+
this.errorContainer = null;
|
|
13
|
+
this.initialize();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
initialize() {
|
|
17
|
+
// Wait for DOM to be ready
|
|
18
|
+
if (document.readyState === 'loading') {
|
|
19
|
+
document.addEventListener('DOMContentLoaded', () => this.setupForm());
|
|
20
|
+
} else {
|
|
21
|
+
this.setupForm();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setupForm() {
|
|
26
|
+
// Find the form and error container
|
|
27
|
+
this.form = document.querySelector('form[data-job-start]');
|
|
28
|
+
this.errorContainer = document.getElementById('validation-errors');
|
|
29
|
+
|
|
30
|
+
if (!this.form) {
|
|
31
|
+
console.warn('JobStartForm: No form found with data-job-start attribute');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!this.errorContainer) {
|
|
36
|
+
console.warn('JobStartForm: No validation-errors container found');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Add submit handler
|
|
41
|
+
this.form.addEventListener('submit', (e) => this.handleSubmit(e));
|
|
42
|
+
|
|
43
|
+
// Add validation on field change
|
|
44
|
+
this.form.addEventListener('change', () => this.clearErrors());
|
|
45
|
+
|
|
46
|
+
console.log('JobStartForm initialized for', this.agentPath);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async handleSubmit(event) {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
|
|
52
|
+
// Clear previous errors
|
|
53
|
+
this.clearErrors();
|
|
54
|
+
|
|
55
|
+
// Validate both agent parameters and method fields
|
|
56
|
+
const [agentParamsValid, methodFieldsValid] = await Promise.all([
|
|
57
|
+
this.validateAgentParameters(),
|
|
58
|
+
this.validateMethodFields()
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
if (agentParamsValid && methodFieldsValid) {
|
|
62
|
+
// All validation passed, submit the form
|
|
63
|
+
this.submitForm();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async validateAgentParameters() {
|
|
68
|
+
const encryptedParams = this.getEncryptedAgentParameters();
|
|
69
|
+
|
|
70
|
+
if (!encryptedParams) {
|
|
71
|
+
return true; // No agent parameters to validate
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const response = await fetch(
|
|
76
|
+
`${this.agentPath}/validate-agent-parameters`,
|
|
77
|
+
{
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
'X-API-Key': this.getApiKey()
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
encrypted_agent_parameters: encryptedParams,
|
|
85
|
+
}),
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const result = await response.json();
|
|
90
|
+
|
|
91
|
+
if (!result.valid) {
|
|
92
|
+
this.displayAgentParameterErrors(result);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return true;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('Agent parameter validation failed:', error);
|
|
99
|
+
this.displayError('Agent parameter validation failed due to network error');
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async validateMethodFields() {
|
|
105
|
+
const formData = this.getFormData();
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const response = await fetch(
|
|
109
|
+
`${this.agentPath}/validate-method-fields`,
|
|
110
|
+
{
|
|
111
|
+
method: 'POST',
|
|
112
|
+
headers: {
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
'X-API-Key': this.getApiKey()
|
|
115
|
+
},
|
|
116
|
+
body: JSON.stringify({
|
|
117
|
+
method_name: 'job_start',
|
|
118
|
+
job_fields: formData,
|
|
119
|
+
}),
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const result = await response.json();
|
|
124
|
+
|
|
125
|
+
if (!result.valid) {
|
|
126
|
+
this.displayMethodFieldErrors(result);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return true;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('Method field validation failed:', error);
|
|
133
|
+
this.displayError('Method field validation failed due to network error');
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getFormData() {
|
|
139
|
+
const formData = {};
|
|
140
|
+
const formElements = this.form.elements;
|
|
141
|
+
|
|
142
|
+
for (let element of formElements) {
|
|
143
|
+
if (element.name && element.type !== 'submit') {
|
|
144
|
+
if (element.type === 'checkbox') {
|
|
145
|
+
formData[element.name] = element.checked;
|
|
146
|
+
} else if (element.type === 'radio') {
|
|
147
|
+
if (element.checked) {
|
|
148
|
+
formData[element.name] = element.value;
|
|
149
|
+
}
|
|
150
|
+
} else if (element.type === 'select-multiple') {
|
|
151
|
+
formData[element.name] = Array.from(element.selectedOptions).map(option => option.value);
|
|
152
|
+
} else {
|
|
153
|
+
formData[element.name] = element.value;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return formData;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getEncryptedAgentParameters() {
|
|
162
|
+
// Look for encrypted agent parameters in hidden fields or data attributes
|
|
163
|
+
const encryptedField = this.form.querySelector('[name="encrypted_agent_parameters"]');
|
|
164
|
+
if (encryptedField) {
|
|
165
|
+
return encryptedField.value;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check if agent parameters are stored in data attributes
|
|
169
|
+
const agentParamsData = this.form.dataset.agentParameters;
|
|
170
|
+
if (agentParamsData) {
|
|
171
|
+
try {
|
|
172
|
+
return JSON.parse(agentParamsData);
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.warn('Failed to parse agent parameters from data attribute');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
getApiKey() {
|
|
182
|
+
// Get API key from form data, meta tag, or global variable
|
|
183
|
+
const apiKeyField = this.form.querySelector('[name="api_key"]');
|
|
184
|
+
if (apiKeyField) {
|
|
185
|
+
return apiKeyField.value;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const metaApiKey = document.querySelector('meta[name="api-key"]');
|
|
189
|
+
if (metaApiKey) {
|
|
190
|
+
return metaApiKey.content;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check for global variable
|
|
194
|
+
if (typeof window.SUPERVAIZER_API_KEY !== 'undefined') {
|
|
195
|
+
return window.SUPERVAIZER_API_KEY;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return '';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
displayAgentParameterErrors(validationResult) {
|
|
202
|
+
const errorSection = document.getElementById('agent-parameter-errors');
|
|
203
|
+
if (!errorSection) {
|
|
204
|
+
// Create error section if it doesn't exist
|
|
205
|
+
const section = document.createElement('div');
|
|
206
|
+
section.id = 'agent-parameter-errors';
|
|
207
|
+
section.className = 'mb-4';
|
|
208
|
+
this.errorContainer.appendChild(section);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const section = document.getElementById('agent-parameter-errors');
|
|
212
|
+
section.innerHTML = `
|
|
213
|
+
<div class="alert alert-warning">
|
|
214
|
+
<strong>Agent Configuration Issues:</strong> ${validationResult.message
|
|
215
|
+
}
|
|
216
|
+
<ul>
|
|
217
|
+
${validationResult.errors
|
|
218
|
+
.map((error) => `<li>${error}</li>`)
|
|
219
|
+
.join('')}
|
|
220
|
+
</ul>
|
|
221
|
+
</div>
|
|
222
|
+
`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
displayMethodFieldErrors(validationResult) {
|
|
226
|
+
// Clear any existing field-specific errors
|
|
227
|
+
this.form.querySelectorAll('.is-invalid').forEach((field) => {
|
|
228
|
+
field.classList.remove('is-invalid');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Display general error message
|
|
232
|
+
this.displayError(validationResult.message);
|
|
233
|
+
|
|
234
|
+
// Mark invalid fields and show specific error messages
|
|
235
|
+
Object.entries(validationResult.invalid_fields).forEach(([fieldName, errorMessage]) => {
|
|
236
|
+
const field = this.form.querySelector(`[name="${fieldName}"]`);
|
|
237
|
+
if (field) {
|
|
238
|
+
field.classList.add('is-invalid');
|
|
239
|
+
|
|
240
|
+
// Add error message below the field
|
|
241
|
+
let errorElement = field.parentNode.querySelector('.invalid-feedback');
|
|
242
|
+
if (!errorElement) {
|
|
243
|
+
errorElement = document.createElement('div');
|
|
244
|
+
errorElement.className = 'invalid-feedback';
|
|
245
|
+
field.parentNode.appendChild(errorElement);
|
|
246
|
+
}
|
|
247
|
+
errorElement.textContent = errorMessage;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
displayError(message) {
|
|
253
|
+
this.errorContainer.innerHTML = `
|
|
254
|
+
<div class="alert alert-danger">
|
|
255
|
+
<strong>Validation Error:</strong> ${message}
|
|
256
|
+
</div>
|
|
257
|
+
`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
clearErrors() {
|
|
261
|
+
this.errorContainer.innerHTML = '';
|
|
262
|
+
|
|
263
|
+
// Clear agent parameter errors
|
|
264
|
+
const agentErrorSection = document.getElementById('agent-parameter-errors');
|
|
265
|
+
if (agentErrorSection) {
|
|
266
|
+
agentErrorSection.innerHTML = '';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Clear field errors
|
|
270
|
+
this.form.querySelectorAll('.is-invalid').forEach((field) => {
|
|
271
|
+
field.classList.remove('is-invalid');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Clear invalid feedback messages
|
|
275
|
+
this.form.querySelectorAll('.invalid-feedback').forEach((feedback) => {
|
|
276
|
+
feedback.remove();
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async submitForm() {
|
|
281
|
+
try {
|
|
282
|
+
const formData = this.getFormData();
|
|
283
|
+
const encryptedParams = this.getEncryptedAgentParameters();
|
|
284
|
+
|
|
285
|
+
const requestBody = {
|
|
286
|
+
job_context: {
|
|
287
|
+
// Add any job context data here
|
|
288
|
+
},
|
|
289
|
+
job_fields: formData
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
if (encryptedParams) {
|
|
293
|
+
requestBody.encrypted_agent_parameters = encryptedParams;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const response = await fetch(`${this.agentPath}/jobs`, {
|
|
297
|
+
method: 'POST',
|
|
298
|
+
headers: {
|
|
299
|
+
'Content-Type': 'application/json',
|
|
300
|
+
'X-API-Key': this.getApiKey()
|
|
301
|
+
},
|
|
302
|
+
body: JSON.stringify(requestBody)
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
if (response.ok) {
|
|
306
|
+
const result = await response.json();
|
|
307
|
+
this.handleJobStarted(result);
|
|
308
|
+
} else {
|
|
309
|
+
const error = await response.json();
|
|
310
|
+
this.handleJobError(error);
|
|
311
|
+
}
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error('Job submission failed:', error);
|
|
314
|
+
this.displayError('Failed to submit job due to network error');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
handleJobStarted(jobData) {
|
|
319
|
+
// Clear form and show success message
|
|
320
|
+
this.form.reset();
|
|
321
|
+
this.clearErrors();
|
|
322
|
+
|
|
323
|
+
this.errorContainer.innerHTML = `
|
|
324
|
+
<div class="alert alert-success">
|
|
325
|
+
<strong>Job Started Successfully!</strong><br>
|
|
326
|
+
Job ID: ${jobData.id || 'Unknown'}<br>
|
|
327
|
+
Status: ${jobData.status || 'Unknown'}
|
|
328
|
+
</div>
|
|
329
|
+
`;
|
|
330
|
+
|
|
331
|
+
// Trigger any success callbacks
|
|
332
|
+
if (typeof this.onJobStarted === 'function') {
|
|
333
|
+
this.onJobStarted(jobData);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
handleJobError(error) {
|
|
338
|
+
this.displayError(`Job submission failed: ${error.detail || error.message || 'Unknown error'}`);
|
|
339
|
+
|
|
340
|
+
// Trigger any error callbacks
|
|
341
|
+
if (typeof this.onJobError === 'function') {
|
|
342
|
+
this.onJobError(error);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Public methods for external use
|
|
347
|
+
setOnJobStarted(callback) {
|
|
348
|
+
this.onJobStarted = callback;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
setOnJobError(callback) {
|
|
352
|
+
this.onJobError = callback;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Method to manually trigger validation
|
|
356
|
+
async validate() {
|
|
357
|
+
const [agentParamsValid, methodFieldsValid] = await Promise.all([
|
|
358
|
+
this.validateAgentParameters(),
|
|
359
|
+
this.validateMethodFields()
|
|
360
|
+
]);
|
|
361
|
+
return agentParamsValid && methodFieldsValid;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Export for module systems
|
|
366
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
367
|
+
module.exports = JobStartForm;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Make available globally
|
|
371
|
+
if (typeof window !== 'undefined') {
|
|
372
|
+
window.JobStartForm = JobStartForm;
|
|
373
|
+
}
|