Spaces:
Running
Running
import { Component, Inject, OnInit } from '@angular/core'; | |
import { CommonModule } from '@angular/common'; | |
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormArray } from '@angular/forms'; | |
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; | |
import { MatTabsModule } from '@angular/material/tabs'; | |
import { MatFormFieldModule } from '@angular/material/form-field'; | |
import { MatInputModule } from '@angular/material/input'; | |
import { MatSelectModule } from '@angular/material/select'; | |
import { MatCheckboxModule } from '@angular/material/checkbox'; | |
import { MatButtonModule } from '@angular/material/button'; | |
import { MatIconModule } from '@angular/material/icon'; | |
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; | |
import { MatDividerModule } from '@angular/material/divider'; | |
import { MatExpansionModule } from '@angular/material/expansion'; | |
import { MatChipsModule } from '@angular/material/chips'; | |
import { ApiService } from '../../services/api.service'; | |
({ | |
selector: 'app-api-edit-dialog', | |
standalone: true, | |
imports: [ | |
CommonModule, | |
ReactiveFormsModule, | |
MatDialogModule, | |
MatTabsModule, | |
MatFormFieldModule, | |
MatInputModule, | |
MatSelectModule, | |
MatCheckboxModule, | |
MatButtonModule, | |
MatIconModule, | |
MatSnackBarModule, | |
MatDividerModule, | |
MatExpansionModule, | |
MatChipsModule | |
], | |
templateUrl: './api-edit-dialog.component.html', | |
styleUrls: ['./api-edit-dialog.component.scss'] | |
}) | |
export default class ApiEditDialogComponent implements OnInit { | |
form!: FormGroup; | |
saving = false; | |
testing = false; | |
testResult: any = null; | |
httpMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']; | |
retryStrategies = ['static', 'exponential']; | |
// Template variable helpers | |
templateVariables = [ | |
{ key: 'variables.origin', desc: 'Origin city from session' }, | |
{ key: 'variables.destination', desc: 'Destination city from session' }, | |
{ key: 'variables.flight_date', desc: 'Flight date from session' }, | |
{ key: 'auth_tokens.api_name.token', desc: 'Auth token for this API' }, | |
{ key: 'config.work_mode', desc: 'Current work mode' } | |
]; | |
constructor( | |
private fb: FormBuilder, | |
private apiService: ApiService, | |
private snackBar: MatSnackBar, | |
public dialogRef: MatDialogRef<ApiEditDialogComponent>, | |
public data: any (MAT_DIALOG_DATA) | |
) {} | |
ngOnInit() { | |
this.initializeForm(); | |
if (this.data.mode === 'edit' && this.data.api) { | |
this.populateForm(this.data.api); | |
} else if (this.data.mode === 'duplicate' && this.data.api) { | |
const duplicateData = { ...this.data.api }; | |
duplicateData.name = duplicateData.name + '_copy'; | |
delete duplicateData.last_update_date; | |
this.populateForm(duplicateData); | |
} | |
} | |
initializeForm() { | |
this.form = this.fb.group({ | |
// General Tab | |
name: ['', [Validators.required, Validators.pattern(/^[a-zA-Z0-9_]+$/)]], | |
url: ['', [Validators.required, Validators.pattern(/^https?:\/\/.+/)]], | |
method: ['POST', Validators.required], | |
body_template: ['{}'], | |
timeout_seconds: [10, [Validators.required, Validators.min(1), Validators.max(300)]], | |
response_prompt: [''], | |
// Headers Tab | |
headers: this.fb.array([]), | |
// Retry Settings | |
retry: this.fb.group({ | |
retry_count: [3, [Validators.required, Validators.min(0), Validators.max(10)]], | |
backoff_seconds: [2, [Validators.required, Validators.min(1), Validators.max(60)]], | |
strategy: ['static', Validators.required] | |
}), | |
// Auth Tab | |
auth: this.fb.group({ | |
enabled: [false], | |
token_endpoint: [''], | |
response_token_path: ['token'], | |
token_request_body: ['{}'], | |
token_refresh_endpoint: [''], | |
token_refresh_body: ['{}'] | |
}), | |
// Proxy (optional) | |
proxy: [''], | |
// For race condition handling | |
last_update_date: [''] | |
}); | |
// Watch for auth enabled changes | |
this.form.get('auth.enabled')?.valueChanges.subscribe(enabled => { | |
const authGroup = this.form.get('auth'); | |
if (enabled) { | |
authGroup?.get('token_endpoint')?.setValidators([Validators.required]); | |
authGroup?.get('response_token_path')?.setValidators([Validators.required]); | |
} else { | |
authGroup?.get('token_endpoint')?.clearValidators(); | |
authGroup?.get('response_token_path')?.clearValidators(); | |
} | |
authGroup?.get('token_endpoint')?.updateValueAndValidity(); | |
authGroup?.get('response_token_path')?.updateValueAndValidity(); | |
}); | |
} | |
populateForm(api: any) { | |
// Convert headers object to FormArray | |
const headersArray = this.form.get('headers') as FormArray; | |
headersArray.clear(); | |
if (api.headers && typeof api.headers === 'object') { | |
Object.entries(api.headers).forEach(([key, value]) => { | |
headersArray.push(this.createHeaderFormGroup(key, value as string)); | |
}); | |
} | |
// Convert body_template to JSON string if it's an object | |
if (api.body_template && typeof api.body_template === 'object') { | |
api.body_template = JSON.stringify(api.body_template, null, 2); | |
} | |
// Convert auth bodies to JSON strings | |
if (api.auth) { | |
if (api.auth.token_request_body && typeof api.auth.token_request_body === 'object') { | |
api.auth.token_request_body = JSON.stringify(api.auth.token_request_body, null, 2); | |
} | |
if (api.auth.token_refresh_body && typeof api.auth.token_refresh_body === 'object') { | |
api.auth.token_refresh_body = JSON.stringify(api.auth.token_refresh_body, null, 2); | |
} | |
} | |
// Patch form values | |
this.form.patchValue(api); | |
// Disable name field if editing | |
if (this.data.mode === 'edit') { | |
this.form.get('name')?.disable(); | |
} | |
} | |
get headers() { | |
return this.form.get('headers') as FormArray; | |
} | |
createHeaderFormGroup(key = '', value = ''): FormGroup { | |
return this.fb.group({ | |
key: [key, Validators.required], | |
value: [value, Validators.required] | |
}); | |
} | |
addHeader() { | |
this.headers.push(this.createHeaderFormGroup()); | |
} | |
removeHeader(index: number) { | |
this.headers.removeAt(index); | |
} | |
insertTemplateVariable(field: string, variable: string) { | |
const control = field.includes('.') | |
? this.form.get(field) | |
: this.form.get(field); | |
if (control) { | |
const currentValue = control.value || ''; | |
const newValue = currentValue + `{{${variable}}}`; | |
control.setValue(newValue); | |
} | |
} | |
validateJSON(field: string): boolean { | |
const control = this.form.get(field); | |
if (!control || !control.value) return true; | |
try { | |
JSON.parse(control.value); | |
return true; | |
} catch { | |
return false; | |
} | |
} | |
async testAPI() { | |
// Validate form first | |
const generalValid = this.form.get('url')?.valid && this.form.get('method')?.valid; | |
if (!generalValid) { | |
this.snackBar.open('Please fill in required fields first', 'Close', { duration: 3000 }); | |
return; | |
} | |
this.testing = true; | |
this.testResult = null; | |
try { | |
const testData = this.prepareAPIData(); | |
// For test, we'll use a sample request | |
const result = await this.apiService.testAPI(testData).toPromise(); | |
this.testResult = result; | |
if (result.success) { | |
this.snackBar.open('API test successful!', 'Close', { duration: 3000 }); | |
} else { | |
this.snackBar.open(`API test failed: ${result.error}`, 'Close', { | |
duration: 5000, | |
panelClass: 'error-snackbar' | |
}); | |
} | |
} catch (error: any) { | |
this.testResult = { | |
success: false, | |
error: error.message || 'Test failed' | |
}; | |
this.snackBar.open('API test failed', 'Close', { | |
duration: 3000, | |
panelClass: 'error-snackbar' | |
}); | |
} finally { | |
this.testing = false; | |
} | |
} | |
prepareAPIData(): any { | |
const formValue = this.form.getRawValue(); | |
// Convert headers array back to object | |
const headers: any = {}; | |
formValue.headers.forEach((h: any) => { | |
if (h.key && h.value) { | |
headers[h.key] = h.value; | |
} | |
}); | |
// Parse JSON fields | |
let body_template = {}; | |
let auth_token_request_body = {}; | |
let auth_token_refresh_body = {}; | |
try { | |
body_template = formValue.body_template ? JSON.parse(formValue.body_template) : {}; | |
} catch (e) { | |
console.error('Invalid body_template JSON:', e); | |
} | |
try { | |
auth_token_request_body = formValue.auth.token_request_body ? JSON.parse(formValue.auth.token_request_body) : {}; | |
} catch (e) { | |
console.error('Invalid auth token_request_body JSON:', e); | |
} | |
try { | |
auth_token_refresh_body = formValue.auth.token_refresh_body ? JSON.parse(formValue.auth.token_refresh_body) : {}; | |
} catch (e) { | |
console.error('Invalid auth token_refresh_body JSON:', e); | |
} | |
// Prepare final data | |
const apiData: any = { | |
name: formValue.name, | |
url: formValue.url, | |
method: formValue.method, | |
headers, // Now it's a dictionary object | |
body_template, | |
timeout_seconds: formValue.timeout_seconds, | |
retry: formValue.retry, | |
response_prompt: formValue.response_prompt | |
}; | |
// Add proxy if specified | |
if (formValue.proxy) { | |
apiData.proxy = formValue.proxy; | |
} | |
// Add auth if enabled | |
if (formValue.auth.enabled) { | |
apiData.auth = { | |
enabled: true, | |
token_endpoint: formValue.auth.token_endpoint, | |
response_token_path: formValue.auth.response_token_path, | |
token_request_body: auth_token_request_body | |
}; | |
if (formValue.auth.token_refresh_endpoint) { | |
apiData.auth.token_refresh_endpoint = formValue.auth.token_refresh_endpoint; | |
apiData.auth.token_refresh_body = auth_token_refresh_body; | |
} | |
} | |
// Add last_update_date for edit mode | |
if (this.data.mode === 'edit' && formValue.last_update_date) { | |
apiData.last_update_date = formValue.last_update_date; | |
} | |
return apiData; | |
} | |
async save() { | |
if (this.form.invalid) { | |
// Mark all fields as touched to show validation errors | |
Object.keys(this.form.controls).forEach(key => { | |
this.form.get(key)?.markAsTouched(); | |
}); | |
// Check specific JSON fields | |
const jsonFields = ['body_template', 'auth.token_request_body', 'auth.token_refresh_body']; | |
for (const field of jsonFields) { | |
if (!this.validateJSON(field)) { | |
this.snackBar.open(`Invalid JSON in ${field}`, 'Close', { | |
duration: 3000, | |
panelClass: 'error-snackbar' | |
}); | |
return; | |
} | |
} | |
this.snackBar.open('Please fix validation errors', 'Close', { duration: 3000 }); | |
return; | |
} | |
this.saving = true; | |
try { | |
const apiData = this.prepareAPIData(); | |
if (this.data.mode === 'create' || this.data.mode === 'duplicate') { | |
await this.apiService.createAPI(apiData).toPromise(); | |
this.snackBar.open('API created successfully', 'Close', { duration: 3000 }); | |
} else { | |
await this.apiService.updateAPI(this.data.api.name, apiData).toPromise(); | |
this.snackBar.open('API updated successfully', 'Close', { duration: 3000 }); | |
} | |
this.dialogRef.close(true); | |
} catch (error: any) { | |
const message = error.error?.detail || | |
(this.data.mode === 'create' ? 'Failed to create API' : 'Failed to update API'); | |
this.snackBar.open(message, 'Close', { | |
duration: 5000, | |
panelClass: 'error-snackbar' | |
}); | |
} finally { | |
this.saving = false; | |
} | |
} | |
cancel() { | |
this.dialogRef.close(false); | |
} | |
} |