flare / flare-ui /src /app /dialogs /api-edit-dialog /api-edit-dialog.component.ts
ciyidogan's picture
Update flare-ui/src/app/dialogs/api-edit-dialog/api-edit-dialog.component.ts
0e360c0 verified
raw
history blame
11.8 kB
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';
@Component({
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>,
@Inject(MAT_DIALOG_DATA) public data: any
) {}
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);
}
}