Spaces:
Paused
Paused
| import { Component, Inject, OnInit } from '@angular/core'; | |
| import { CommonModule } from '@angular/common'; | |
| import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormArray } from '@angular/forms'; | |
| import { FormsModule } 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 { MatMenuModule } from '@angular/material/menu'; | |
| import { MatTableModule } from '@angular/material/table'; | |
| import { ApiService } from '../../services/api.service'; | |
| import { JsonEditorComponent } from '../../shared/json-editor/json-editor.component'; | |
| ({ | |
| selector: 'app-api-edit-dialog', | |
| standalone: true, | |
| imports: [ | |
| CommonModule, | |
| ReactiveFormsModule, | |
| FormsModule, | |
| MatDialogModule, | |
| MatTabsModule, | |
| MatFormFieldModule, | |
| MatInputModule, | |
| MatSelectModule, | |
| MatCheckboxModule, | |
| MatButtonModule, | |
| MatIconModule, | |
| MatSnackBarModule, | |
| MatDividerModule, | |
| MatExpansionModule, | |
| MatChipsModule, | |
| MatMenuModule, | |
| MatTableModule, | |
| JsonEditorComponent | |
| ], | |
| 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; | |
| testRequestJson = '{}'; | |
| allIntentParameters: string[] = []; | |
| responseMappingVariables: string[] = []; | |
| activeTabIndex = 0; | |
| httpMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']; | |
| retryStrategies = ['static', 'exponential']; | |
| variableTypes = ['str', 'int', 'float', 'bool', 'date']; | |
| constructor( | |
| private fb: FormBuilder, | |
| private apiService: ApiService, | |
| private snackBar: MatSnackBar, | |
| public dialogRef: MatDialogRef<ApiEditDialogComponent>, | |
| (MAT_DIALOG_DATA) public data: any | |
| ) {} | |
| ngOnInit() { | |
| this.initializeForm(); | |
| this.loadIntentParameters(); | |
| // Aktif tab'ı ayarla | |
| if (this.data.activeTab !== undefined) { | |
| this.activeTabIndex = this.data.activeTab; | |
| } | |
| if ((this.data.mode === 'edit' || this.data.mode === 'test') && 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); | |
| } | |
| // Test modunda açıldıysa test JSON'ını hazırla | |
| if (this.data.mode === 'test') { | |
| setTimeout(() => { | |
| this.updateTestRequestJson(); | |
| }, 100); | |
| } | |
| // Watch response mappings changes | |
| this.form.get('response_mappings')?.valueChanges.subscribe(() => { | |
| this.updateResponseMappingVariables(); | |
| }); | |
| } | |
| 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: [''], | |
| response_mappings: this.fb.array([]), | |
| // 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) { | |
| console.log('Populating form with API:', api); | |
| // Convert headers object to FormArray | |
| const headersArray = this.form.get('headers') as FormArray; | |
| headersArray.clear(); | |
| if (api.headers) { | |
| if (Array.isArray(api.headers)) { | |
| api.headers.forEach((header: any) => { | |
| headersArray.push(this.createHeaderFormGroup(header.key || '', header.value || '')); | |
| }); | |
| } else if (typeof api.headers === 'object') { | |
| Object.entries(api.headers).forEach(([key, value]) => { | |
| headersArray.push(this.createHeaderFormGroup(key, value as string)); | |
| }); | |
| } | |
| } | |
| // Convert response_mappings to FormArray | |
| const responseMappingsArray = this.form.get('response_mappings') as FormArray; | |
| responseMappingsArray.clear(); | |
| if (api.response_mappings && Array.isArray(api.response_mappings)) { | |
| api.response_mappings.forEach((mapping: any) => { | |
| responseMappingsArray.push(this.createResponseMappingFormGroup(mapping)); | |
| }); | |
| } | |
| // 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); | |
| } | |
| } | |
| const formData = { ...api }; | |
| // headers array'ini kaldır çünkü zaten FormArray'e ekledik | |
| delete formData.headers; | |
| delete formData.response_mappings; | |
| // Patch form values | |
| this.form.patchValue(formData); | |
| // Disable name field if editing or testing | |
| if (this.data.mode === 'edit' || this.data.mode === 'test') { | |
| this.form.get('name')?.disable(); | |
| } | |
| } | |
| get headers() { | |
| return this.form.get('headers') as FormArray; | |
| } | |
| get responseMappings() { | |
| return this.form.get('response_mappings') as FormArray; | |
| } | |
| createHeaderFormGroup(key = '', value = ''): FormGroup { | |
| return this.fb.group({ | |
| key: [key, Validators.required], | |
| value: [value, Validators.required] | |
| }); | |
| } | |
| createResponseMappingFormGroup(data: any = {}): FormGroup { | |
| return this.fb.group({ | |
| variable_name: [data.variable_name || '', [Validators.required, Validators.pattern(/^[a-z_][a-z0-9_]*$/)]], | |
| type: [data.type || 'str', Validators.required], | |
| json_path: [data.json_path || '', Validators.required], | |
| caption: [data.caption || '', Validators.required] | |
| }); | |
| } | |
| addHeader() { | |
| this.headers.push(this.createHeaderFormGroup()); | |
| } | |
| removeHeader(index: number) { | |
| this.headers.removeAt(index); | |
| } | |
| addResponseMapping() { | |
| this.responseMappings.push(this.createResponseMappingFormGroup()); | |
| } | |
| removeResponseMapping(index: number) { | |
| this.responseMappings.removeAt(index); | |
| } | |
| insertHeaderValue(index: number, variable: string) { | |
| const headerGroup = this.headers.at(index); | |
| if (headerGroup) { | |
| const valueControl = headerGroup.get('value'); | |
| if (valueControl) { | |
| const currentValue = valueControl.value || ''; | |
| const newValue = currentValue + `{{${variable}}}`; | |
| valueControl.setValue(newValue); | |
| } | |
| } | |
| } | |
| getTemplateVariables(includeResponseMappings = true): string[] { | |
| const variables = new Set<string>(); | |
| // Intent parameters | |
| this.allIntentParameters.forEach(param => { | |
| variables.add(`variables.${param}`); | |
| }); | |
| // Auth tokens | |
| const apiName = this.form.get('name')?.value || 'api_name'; | |
| variables.add(`auth_tokens.${apiName}.token`); | |
| // Response mappings | |
| if (includeResponseMappings) { | |
| this.responseMappingVariables.forEach(varName => { | |
| variables.add(`variables.${varName}`); | |
| }); | |
| } | |
| // Config variables | |
| variables.add('config.work_mode'); | |
| variables.add('config.cloud_token'); | |
| return Array.from(variables).sort(); | |
| } | |
| updateResponseMappingVariables() { | |
| this.responseMappingVariables = []; | |
| const mappings = this.responseMappings.value; | |
| mappings.forEach((mapping: any) => { | |
| if (mapping.variable_name) { | |
| this.responseMappingVariables.push(mapping.variable_name); | |
| } | |
| }); | |
| } | |
| async loadIntentParameters() { | |
| try { | |
| const projects = await this.apiService.getProjects(false).toPromise(); | |
| const params = new Set<string>(); | |
| projects?.forEach(project => { | |
| project.versions?.forEach(version => { | |
| version.intents?.forEach(intent => { | |
| intent.parameters?.forEach((param: any) => { | |
| if (param.variable_name) { | |
| params.add(param.variable_name); | |
| } | |
| }); | |
| }); | |
| }); | |
| }); | |
| this.allIntentParameters = Array.from(params).sort(); | |
| } catch (error) { | |
| console.error('Failed to load intent parameters:', error); | |
| } | |
| } | |
| // JSON validation için replacer fonksiyonu | |
| replaceVariablesForValidation = (jsonStr: string): string => { | |
| let processed = jsonStr; | |
| processed = processed.replace(/\{\{([^}]+)\}\}/g, (match, variablePath) => { | |
| if (variablePath.includes('variables.')) { | |
| const varName = variablePath.split('.').pop()?.toLowerCase() || ''; | |
| const numericVars = ['count', 'passenger_count', 'timeout_seconds', 'retry_count', 'amount', 'price', 'quantity', 'age', 'id']; | |
| const booleanVars = ['enabled', 'published', 'is_active', 'confirmed', 'canceled', 'deleted', 'required']; | |
| if (numericVars.some(v => varName.includes(v))) { | |
| return '1'; | |
| } else if (booleanVars.some(v => varName.includes(v))) { | |
| return 'true'; | |
| } else { | |
| return '"placeholder"'; | |
| } | |
| } | |
| return '"placeholder"'; | |
| }); | |
| return processed; | |
| } | |
| async testAPI() { | |
| 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(); | |
| let testRequestData = {}; | |
| try { | |
| testRequestData = JSON.parse(this.testRequestJson); | |
| } catch (e) { | |
| this.snackBar.open('Invalid test request JSON', 'Close', { | |
| duration: 3000, | |
| panelClass: 'error-snackbar' | |
| }); | |
| this.testing = false; | |
| return; | |
| } | |
| testData.test_request = testRequestData; | |
| const result = await this.apiService.testAPI(testData).toPromise(); | |
| // Response headers'ı obje olarak sakla | |
| if (result.response_headers && typeof result.response_headers === 'string') { | |
| try { | |
| result.response_headers = JSON.parse(result.response_headers); | |
| } catch { | |
| // Headers parse edilemezse string olarak bırak | |
| } | |
| } | |
| this.testResult = result; | |
| if (result.success) { | |
| this.snackBar.open(`API test successful! (${result.status_code})`, 'Close', { | |
| duration: 3000 | |
| }); | |
| } else { | |
| const errorMsg = result.error || `API returned status ${result.status_code}`; | |
| this.snackBar.open(`API test failed: ${errorMsg}`, '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; | |
| } | |
| } | |
| updateTestRequestJson() { | |
| const formValue = this.form.getRawValue(); | |
| let bodyTemplate = {}; | |
| try { | |
| bodyTemplate = JSON.parse(formValue.body_template); | |
| } catch { | |
| bodyTemplate = {}; | |
| } | |
| const testData = this.replacePlaceholdersForTest(bodyTemplate); | |
| this.testRequestJson = JSON.stringify(testData, null, 2); | |
| } | |
| replacePlaceholdersForTest(obj: any): any { | |
| if (typeof obj === 'string') { | |
| let result = obj; | |
| result = result.replace(/\{\{variables\.origin\}\}/g, 'Istanbul'); | |
| result = result.replace(/\{\{variables\.destination\}\}/g, 'Ankara'); | |
| result = result.replace(/\{\{variables\.flight_date\}\}/g, '2025-06-15'); | |
| result = result.replace(/\{\{variables\.passenger_count\}\}/g, '2'); | |
| result = result.replace(/\{\{variables\.flight_number\}\}/g, 'TK123'); | |
| result = result.replace(/\{\{variables\.pnr\}\}/g, 'ABC12'); | |
| result = result.replace(/\{\{variables\.surname\}\}/g, 'Test'); | |
| result = result.replace(/\{\{auth_tokens\.[^}]+\.token\}\}/g, 'test_token_123'); | |
| result = result.replace(/\{\{config\.work_mode\}\}/g, 'hfcloud'); | |
| result = result.replace(/\{\{[^}]+\}\}/g, 'test_value'); | |
| return result; | |
| } else if (typeof obj === 'object' && obj !== null) { | |
| const result: any = Array.isArray(obj) ? [] : {}; | |
| for (const key in obj) { | |
| result[key] = this.replacePlaceholdersForTest(obj[key]); | |
| } | |
| return result; | |
| } | |
| return obj; | |
| } | |
| prepareAPIData(): any { | |
| const formValue = this.form.getRawValue(); | |
| const headers: any = {}; | |
| formValue.headers.forEach((h: any) => { | |
| if (h.key && h.value) { | |
| headers[h.key] = h.value; | |
| } | |
| }); | |
| 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); | |
| } | |
| const apiData: any = { | |
| name: formValue.name, | |
| url: formValue.url, | |
| method: formValue.method, | |
| headers, | |
| body_template, | |
| timeout_seconds: formValue.timeout_seconds, | |
| retry: formValue.retry, | |
| response_prompt: formValue.response_prompt, | |
| response_mappings: formValue.response_mappings || [] | |
| }; | |
| // Proxy - null olarak gönder boşsa | |
| apiData.proxy = formValue.proxy || null; | |
| if (formValue.proxy) { | |
| apiData.proxy = formValue.proxy; | |
| } | |
| 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; | |
| } | |
| }else { | |
| // Auth disabled olsa bile null olarak gönder | |
| apiData.auth = null; | |
| } | |
| // Edit modunda last_update_date'i ekle | |
| if (this.data.mode === 'edit' && formValue.last_update_date) { | |
| apiData.last_update_date = formValue.last_update_date; | |
| } | |
| console.log('Prepared API data:', apiData); | |
| return apiData; | |
| } | |
| async save() { | |
| if (this.data.mode === 'test') { | |
| this.cancel(); | |
| return; | |
| } | |
| if (this.form.invalid) { | |
| Object.keys(this.form.controls).forEach(key => { | |
| this.form.get(key)?.markAsTouched(); | |
| }); | |
| 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); | |
| } | |
| } |