ciyidogan commited on
Commit
f3e5f4f
·
verified ·
1 Parent(s): 0866ece

Update flare-ui/src/app/dialogs/api-edit-dialog/api-edit-dialog.component.ts

Browse files
flare-ui/src/app/dialogs/api-edit-dialog/api-edit-dialog.component.ts CHANGED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Component, Inject, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormArray } from '@angular/forms';
4
+ import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
5
+ import { MatTabsModule } from '@angular/material/tabs';
6
+ import { MatFormFieldModule } from '@angular/material/form-field';
7
+ import { MatInputModule } from '@angular/material/input';
8
+ import { MatSelectModule } from '@angular/material/select';
9
+ import { MatCheckboxModule } from '@angular/material/checkbox';
10
+ import { MatButtonModule } from '@angular/material/button';
11
+ import { MatIconModule } from '@angular/material/icon';
12
+ import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
13
+ import { MatDividerModule } from '@angular/material/divider';
14
+ import { MatExpansionModule } from '@angular/material/expansion';
15
+ import { MatChipsModule } from '@angular/material/chips';
16
+ import { ApiService } from '../../services/api.service';
17
+
18
+ @Component({
19
+ selector: 'app-api-edit-dialog',
20
+ standalone: true,
21
+ imports: [
22
+ CommonModule,
23
+ ReactiveFormsModule,
24
+ MatDialogModule,
25
+ MatTabsModule,
26
+ MatFormFieldModule,
27
+ MatInputModule,
28
+ MatSelectModule,
29
+ MatCheckboxModule,
30
+ MatButtonModule,
31
+ MatIconModule,
32
+ MatSnackBarModule,
33
+ MatDividerModule,
34
+ MatExpansionModule,
35
+ MatChipsModule
36
+ ],
37
+ templateUrl: './api-edit-dialog.component.html',
38
+ styleUrls: ['./api-edit-dialog.component.scss']
39
+ })
40
+ export default class ApiEditDialogComponent implements OnInit {
41
+ form!: FormGroup;
42
+ saving = false;
43
+ testing = false;
44
+ testResult: any = null;
45
+
46
+ httpMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
47
+ retryStrategies = ['static', 'exponential'];
48
+
49
+ // Template variable helpers
50
+ templateVariables = [
51
+ { key: 'variables.origin', desc: 'Origin city from session' },
52
+ { key: 'variables.destination', desc: 'Destination city from session' },
53
+ { key: 'variables.flight_date', desc: 'Flight date from session' },
54
+ { key: 'auth_tokens.api_name.token', desc: 'Auth token for this API' },
55
+ { key: 'config.work_mode', desc: 'Current work mode' }
56
+ ];
57
+
58
+ constructor(
59
+ private fb: FormBuilder,
60
+ private apiService: ApiService,
61
+ private snackBar: MatSnackBar,
62
+ public dialogRef: MatDialogRef<ApiEditDialogComponent>,
63
+ @Inject(MAT_DIALOG_DATA) public data: any
64
+ ) {}
65
+
66
+ ngOnInit() {
67
+ this.initializeForm();
68
+
69
+ if (this.data.mode === 'edit' && this.data.api) {
70
+ this.populateForm(this.data.api);
71
+ } else if (this.data.mode === 'duplicate' && this.data.api) {
72
+ const duplicateData = { ...this.data.api };
73
+ duplicateData.name = duplicateData.name + '_copy';
74
+ delete duplicateData.last_update_date;
75
+ this.populateForm(duplicateData);
76
+ }
77
+ }
78
+
79
+ initializeForm() {
80
+ this.form = this.fb.group({
81
+ // General Tab
82
+ name: ['', [Validators.required, Validators.pattern(/^[a-zA-Z0-9_]+$/)]],
83
+ url: ['', [Validators.required, Validators.pattern(/^https?:\/\/.+/)]],
84
+ method: ['POST', Validators.required],
85
+ body_template: ['{}'],
86
+ timeout_seconds: [10, [Validators.required, Validators.min(1), Validators.max(300)]],
87
+ response_prompt: [''],
88
+
89
+ // Headers Tab
90
+ headers: this.fb.array([]),
91
+
92
+ // Retry Settings
93
+ retry: this.fb.group({
94
+ retry_count: [3, [Validators.required, Validators.min(0), Validators.max(10)]],
95
+ backoff_seconds: [2, [Validators.required, Validators.min(1), Validators.max(60)]],
96
+ strategy: ['static', Validators.required]
97
+ }),
98
+
99
+ // Auth Tab
100
+ auth: this.fb.group({
101
+ enabled: [false],
102
+ token_endpoint: [''],
103
+ response_token_path: ['token'],
104
+ token_request_body: ['{}'],
105
+ token_refresh_endpoint: [''],
106
+ token_refresh_body: ['{}']
107
+ }),
108
+
109
+ // Proxy (optional)
110
+ proxy: [''],
111
+
112
+ // For race condition handling
113
+ last_update_date: ['']
114
+ });
115
+
116
+ // Watch for auth enabled changes
117
+ this.form.get('auth.enabled')?.valueChanges.subscribe(enabled => {
118
+ const authGroup = this.form.get('auth');
119
+ if (enabled) {
120
+ authGroup?.get('token_endpoint')?.setValidators([Validators.required]);
121
+ authGroup?.get('response_token_path')?.setValidators([Validators.required]);
122
+ } else {
123
+ authGroup?.get('token_endpoint')?.clearValidators();
124
+ authGroup?.get('response_token_path')?.clearValidators();
125
+ }
126
+ authGroup?.get('token_endpoint')?.updateValueAndValidity();
127
+ authGroup?.get('response_token_path')?.updateValueAndValidity();
128
+ });
129
+ }
130
+
131
+ populateForm(api: any) {
132
+ // Convert headers object to FormArray
133
+ const headersArray = this.form.get('headers') as FormArray;
134
+ headersArray.clear();
135
+
136
+ if (api.headers && typeof api.headers === 'object') {
137
+ Object.entries(api.headers).forEach(([key, value]) => {
138
+ headersArray.push(this.createHeaderFormGroup(key, value as string));
139
+ });
140
+ }
141
+
142
+ // Convert body_template to JSON string if it's an object
143
+ if (api.body_template && typeof api.body_template === 'object') {
144
+ api.body_template = JSON.stringify(api.body_template, null, 2);
145
+ }
146
+
147
+ // Convert auth bodies to JSON strings
148
+ if (api.auth) {
149
+ if (api.auth.token_request_body && typeof api.auth.token_request_body === 'object') {
150
+ api.auth.token_request_body = JSON.stringify(api.auth.token_request_body, null, 2);
151
+ }
152
+ if (api.auth.token_refresh_body && typeof api.auth.token_refresh_body === 'object') {
153
+ api.auth.token_refresh_body = JSON.stringify(api.auth.token_refresh_body, null, 2);
154
+ }
155
+ }
156
+
157
+ // Patch form values
158
+ this.form.patchValue(api);
159
+
160
+ // Disable name field if editing
161
+ if (this.data.mode === 'edit') {
162
+ this.form.get('name')?.disable();
163
+ }
164
+ }
165
+
166
+ get headers() {
167
+ return this.form.get('headers') as FormArray;
168
+ }
169
+
170
+ createHeaderFormGroup(key = '', value = ''): FormGroup {
171
+ return this.fb.group({
172
+ key: [key, Validators.required],
173
+ value: [value, Validators.required]
174
+ });
175
+ }
176
+
177
+ addHeader() {
178
+ this.headers.push(this.createHeaderFormGroup());
179
+ }
180
+
181
+ removeHeader(index: number) {
182
+ this.headers.removeAt(index);
183
+ }
184
+
185
+ insertTemplateVariable(field: string, variable: string) {
186
+ const control = field.includes('.')
187
+ ? this.form.get(field)
188
+ : this.form.get(field);
189
+
190
+ if (control) {
191
+ const currentValue = control.value || '';
192
+ const newValue = currentValue + `{{${variable}}}`;
193
+ control.setValue(newValue);
194
+ }
195
+ }
196
+
197
+ validateJSON(field: string): boolean {
198
+ const control = this.form.get(field);
199
+ if (!control || !control.value) return true;
200
+
201
+ try {
202
+ JSON.parse(control.value);
203
+ return true;
204
+ } catch {
205
+ return false;
206
+ }
207
+ }
208
+
209
+ async testAPI() {
210
+ // Validate form first
211
+ const generalValid = this.form.get('url')?.valid && this.form.get('method')?.valid;
212
+ if (!generalValid) {
213
+ this.snackBar.open('Please fill in required fields first', 'Close', { duration: 3000 });
214
+ return;
215
+ }
216
+
217
+ this.testing = true;
218
+ this.testResult = null;
219
+
220
+ try {
221
+ const testData = this.prepareAPIData();
222
+
223
+ // For test, we'll use a sample request
224
+ const result = await this.apiService.testAPI(testData).toPromise();
225
+ this.testResult = result;
226
+
227
+ if (result.success) {
228
+ this.snackBar.open('API test successful!', 'Close', { duration: 3000 });
229
+ } else {
230
+ this.snackBar.open(`API test failed: ${result.error}`, 'Close', {
231
+ duration: 5000,
232
+ panelClass: 'error-snackbar'
233
+ });
234
+ }
235
+ } catch (error: any) {
236
+ this.testResult = {
237
+ success: false,
238
+ error: error.message || 'Test failed'
239
+ };
240
+ this.snackBar.open('API test failed', 'Close', {
241
+ duration: 3000,
242
+ panelClass: 'error-snackbar'
243
+ });
244
+ } finally {
245
+ this.testing = false;
246
+ }
247
+ }
248
+
249
+ prepareAPIData(): any {
250
+ const formValue = this.form.getRawValue();
251
+
252
+ // Convert headers array back to object
253
+ const headers: any = {};
254
+ formValue.headers.forEach((h: any) => {
255
+ if (h.key && h.value) {
256
+ headers[h.key] = h.value;
257
+ }
258
+ });
259
+
260
+ // Parse JSON fields
261
+ let body_template = {};
262
+ let auth_token_request_body = {};
263
+ let auth_token_refresh_body = {};
264
+
265
+ try {
266
+ body_template = formValue.body_template ? JSON.parse(formValue.body_template) : {};
267
+ } catch {}
268
+
269
+ try {
270
+ auth_token_request_body = formValue.auth.token_request_body ? JSON.parse(formValue.auth.token_request_body) : {};
271
+ } catch {}
272
+
273
+ try {
274
+ auth_token_refresh_body = formValue.auth.token_refresh_body ? JSON.parse(formValue.auth.token_refresh_body) : {};
275
+ } catch {}
276
+
277
+ // Prepare final data
278
+ const apiData: any = {
279
+ name: formValue.name,
280
+ url: formValue.url,
281
+ method: formValue.method,
282
+ headers,
283
+ body_template,
284
+ timeout_seconds: formValue.timeout_seconds,
285
+ retry: formValue.retry,
286
+ response_prompt: formValue.response_prompt
287
+ };
288
+
289
+ // Add proxy if specified
290
+ if (formValue.proxy) {
291
+ apiData.proxy = formValue.proxy;
292
+ }
293
+
294
+ // Add auth if enabled
295
+ if (formValue.auth.enabled) {
296
+ apiData.auth = {
297
+ enabled: true,
298
+ token_endpoint: formValue.auth.token_endpoint,
299
+ response_token_path: formValue.auth.response_token_path,
300
+ token_request_body: auth_token_request_body
301
+ };
302
+
303
+ if (formValue.auth.token_refresh_endpoint) {
304
+ apiData.auth.token_refresh_endpoint = formValue.auth.token_refresh_endpoint;
305
+ apiData.auth.token_refresh_body = auth_token_refresh_body;
306
+ }
307
+ }
308
+
309
+ // Add last_update_date for edit mode
310
+ if (this.data.mode === 'edit' && formValue.last_update_date) {
311
+ apiData.last_update_date = formValue.last_update_date;
312
+ }
313
+
314
+ return apiData;
315
+ }
316
+
317
+ async save() {
318
+ if (this.form.invalid) {
319
+ // Mark all fields as touched to show validation errors
320
+ Object.keys(this.form.controls).forEach(key => {
321
+ this.form.get(key)?.markAsTouched();
322
+ });
323
+
324
+ // Check specific JSON fields
325
+ const jsonFields = ['body_template', 'auth.token_request_body', 'auth.token_refresh_body'];
326
+ for (const field of jsonFields) {
327
+ if (!this.validateJSON(field)) {
328
+ this.snackBar.open(`Invalid JSON in ${field}`, 'Close', {
329
+ duration: 3000,
330
+ panelClass: 'error-snackbar'
331
+ });
332
+ return;
333
+ }
334
+ }
335
+
336
+ this.snackBar.open('Please fix validation errors', 'Close', { duration: 3000 });
337
+ return;
338
+ }
339
+
340
+ this.saving = true;
341
+ try {
342
+ const apiData = this.prepareAPIData();
343
+
344
+ if (this.data.mode === 'create' || this.data.mode === 'duplicate') {
345
+ await this.apiService.createAPI(apiData).toPromise();
346
+ this.snackBar.open('API created successfully', 'Close', { duration: 3000 });
347
+ } else {
348
+ await this.apiService.updateAPI(this.data.api.name, apiData).toPromise();
349
+ this.snackBar.open('API updated successfully', 'Close', { duration: 3000 });
350
+ }
351
+
352
+ this.dialogRef.close(true);
353
+ } catch (error: any) {
354
+ const message = error.error?.detail ||
355
+ (this.data.mode === 'create' ? 'Failed to create API' : 'Failed to update API');
356
+ this.snackBar.open(message, 'Close', {
357
+ duration: 5000,
358
+ panelClass: 'error-snackbar'
359
+ });
360
+ } finally {
361
+ this.saving = false;
362
+ }
363
+ }
364
+
365
+ cancel() {
366
+ this.dialogRef.close(false);
367
+ }
368
+ }