ciyidogan commited on
Commit
c87f24b
·
verified ·
1 Parent(s): c519511

Update flare-ui/src/app/components/apis/apis.component.ts

Browse files
flare-ui/src/app/components/apis/apis.component.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Component, inject, OnInit } from '@angular/core';
2
  import { CommonModule } from '@angular/common';
3
  import { FormsModule } from '@angular/forms';
4
  import { MatDialog, MatDialogModule } from '@angular/material/dialog';
@@ -15,6 +15,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
15
  import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
16
  import { MatDividerModule } from '@angular/material/divider';
17
  import { ApiService, API } from '../../services/api.service';
 
18
 
19
  @Component({
20
  selector: 'app-apis',
@@ -41,15 +42,15 @@ import { ApiService, API } from '../../services/api.service';
41
  <div class="toolbar">
42
  <h2>API Definitions</h2>
43
  <div class="toolbar-actions">
44
- <button mat-raised-button color="primary" (click)="createAPI()">
45
  <mat-icon>add</mat-icon>
46
  New API
47
  </button>
48
- <button mat-button (click)="importAPIs()">
49
  <mat-icon>upload</mat-icon>
50
  Import
51
  </button>
52
- <button mat-button (click)="exportAPIs()">
53
  <mat-icon>download</mat-icon>
54
  Export
55
  </button>
@@ -66,7 +67,16 @@ import { ApiService, API } from '../../services/api.service';
66
 
67
  <mat-progress-bar mode="indeterminate" *ngIf="loading"></mat-progress-bar>
68
 
69
- @if (!loading && filteredAPIs.length === 0) {
 
 
 
 
 
 
 
 
 
70
  <div class="empty-state">
71
  <mat-icon>api</mat-icon>
72
  <p>No APIs found.</p>
@@ -74,6 +84,14 @@ import { ApiService, API } from '../../services/api.service';
74
  Create your first API
75
  </button>
76
  </div>
 
 
 
 
 
 
 
 
77
  } @else if (!loading) {
78
  <table mat-table [dataSource]="filteredAPIs" class="apis-table">
79
  <!-- Name Column -->
@@ -110,7 +128,8 @@ import { ApiService, API } from '../../services/api.service';
110
  <ng-container matColumnDef="auth">
111
  <th mat-header-cell *matHeaderCellDef>Auth</th>
112
  <td mat-cell *matCellDef="let api">
113
- <mat-icon [color]="api.auth?.enabled ? 'primary' : ''">
 
114
  {{ api.auth?.enabled ? 'lock' : 'lock_open' }}
115
  </mat-icon>
116
  </td>
@@ -118,10 +137,12 @@ import { ApiService, API } from '../../services/api.service';
118
 
119
  <!-- Deleted Column -->
120
  <ng-container matColumnDef="deleted">
121
- <th mat-header-cell *matHeaderCellDef>Deleted</th>
122
  <td mat-cell *matCellDef="let api">
123
  @if (api.deleted) {
124
- <mat-icon color="warn">delete</mat-icon>
 
 
125
  }
126
  </td>
127
  </ng-container>
@@ -130,11 +151,17 @@ import { ApiService, API } from '../../services/api.service';
130
  <ng-container matColumnDef="actions">
131
  <th mat-header-cell *matHeaderCellDef>Actions</th>
132
  <td mat-cell *matCellDef="let api">
133
- <button mat-icon-button [matMenuTriggerFor]="menu" (click)="$event.stopPropagation()">
134
- <mat-icon>more_vert</mat-icon>
 
 
 
 
 
 
135
  </button>
136
  <mat-menu #menu="matMenu">
137
- <button mat-menu-item (click)="editAPI(api)">
138
  <mat-icon>edit</mat-icon>
139
  <span>Edit</span>
140
  </button>
@@ -152,6 +179,12 @@ import { ApiService, API } from '../../services/api.service';
152
  <mat-icon color="warn">delete</mat-icon>
153
  <span>Delete</span>
154
  </button>
 
 
 
 
 
 
155
  }
156
  </mat-menu>
157
  </td>
@@ -192,7 +225,7 @@ import { ApiService, API } from '../../services/api.service';
192
  }
193
  }
194
 
195
- .empty-state {
196
  text-align: center;
197
  padding: 60px 20px;
198
  background-color: white;
@@ -214,6 +247,12 @@ import { ApiService, API } from '../../services/api.service';
214
  }
215
  }
216
 
 
 
 
 
 
 
217
  .apis-table {
218
  width: 100%;
219
  background: white;
@@ -253,8 +292,13 @@ import { ApiService, API } from '../../services/api.service';
253
  &.deleted-row {
254
  opacity: 0.6;
255
  background-color: #fafafa;
 
256
  }
257
  }
 
 
 
 
258
  }
259
  }
260
 
@@ -271,16 +315,19 @@ import { ApiService, API } from '../../services/api.service';
271
  }
272
  `]
273
  })
274
- export class ApisComponent implements OnInit {
275
  private apiService = inject(ApiService);
276
  private dialog = inject(MatDialog);
277
  private snackBar = inject(MatSnackBar);
 
278
 
279
  apis: API[] = [];
280
  filteredAPIs: API[] = [];
281
  loading = true;
 
282
  showDeleted = false;
283
  searchTerm = '';
 
284
 
285
  displayedColumns: string[] = ['name', 'url', 'method', 'timeout', 'auth', 'deleted', 'actions'];
286
 
@@ -288,16 +335,26 @@ export class ApisComponent implements OnInit {
288
  this.loadAPIs();
289
  }
290
 
 
 
 
 
 
291
  loadAPIs() {
292
  this.loading = true;
293
- this.apiService.getAPIs(this.showDeleted).subscribe({
 
 
 
 
294
  next: (apis) => {
295
  this.apis = apis;
296
  this.filterAPIs();
297
  this.loading = false;
298
  },
299
  error: (err) => {
300
- this.snackBar.open('Failed to load APIs', 'Close', {
 
301
  duration: 5000,
302
  panelClass: 'error-snackbar'
303
  });
@@ -307,107 +364,189 @@ export class ApisComponent implements OnInit {
307
  }
308
 
309
  filterAPIs() {
310
- const term = this.searchTerm.toLowerCase();
311
- this.filteredAPIs = this.apis.filter(api =>
312
- api.name.toLowerCase().includes(term) ||
313
- api.url.toLowerCase().includes(term)
314
- );
 
 
 
 
 
315
  }
316
 
317
  async createAPI() {
318
- const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component');
319
-
320
- const dialogRef = this.dialog.open(ApiEditDialogComponent, {
321
- width: '800px',
322
- data: { mode: 'create' }
323
- });
 
 
324
 
325
- dialogRef.afterClosed().subscribe((result: any) => {
326
- if (result) {
327
- this.loadAPIs();
328
- }
329
- });
 
 
 
 
 
 
 
 
 
330
  }
331
 
332
  async editAPI(api: API) {
333
- const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component');
334
 
335
- const dialogRef = this.dialog.open(ApiEditDialogComponent, {
336
- width: '800px',
337
- data: { mode: 'edit', api: { ...api } }
338
- });
 
 
 
 
339
 
340
- dialogRef.afterClosed().subscribe((result: any) => {
341
- if (result) {
342
- this.loadAPIs();
343
- }
344
- });
 
 
 
 
 
 
 
 
 
345
  }
346
 
347
  async testAPI(api: API) {
348
- const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component');
349
-
350
- const dialogRef = this.dialog.open(ApiEditDialogComponent, {
351
- width: '800px',
352
- data: {
353
- mode: 'test', // Test modunda açıyoruz
354
- api: { ...api },
355
- activeTab: 4 // Test tab'ını aktif yap (0-based index)
356
- }
357
- });
 
 
358
 
359
- dialogRef.afterClosed().subscribe((result: any) => {
360
- if (result) {
361
- this.loadAPIs();
362
- }
363
- });
 
 
 
 
 
 
 
 
 
364
  }
365
 
366
  async duplicateAPI(api: API) {
367
- const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component');
368
-
369
- const dialogRef = this.dialog.open(ApiEditDialogComponent, {
370
- width: '800px',
371
- data: { mode: 'duplicate', api: { ...api } }
372
- });
 
 
 
 
 
 
373
 
374
- dialogRef.afterClosed().subscribe((result: any) => {
375
- if (result) {
376
- this.loadAPIs();
377
- }
378
- });
 
 
 
 
 
 
 
 
 
379
  }
380
 
381
  async deleteAPI(api: API) {
382
- const { default: ConfirmDialogComponent } = await import('../../dialogs/confirm-dialog/confirm-dialog.component');
383
 
384
- const dialogRef = this.dialog.open(ConfirmDialogComponent, {
385
- width: '400px',
386
- data: {
387
- title: 'Delete API',
388
- message: `Are you sure you want to delete "${api.name}"?`,
389
- confirmText: 'Delete',
390
- confirmColor: 'warn'
391
- }
392
- });
 
 
 
393
 
394
- dialogRef.afterClosed().subscribe((confirmed) => {
395
- if (confirmed) {
396
- this.apiService.deleteAPI(api.name).subscribe({
397
- next: () => {
398
- this.snackBar.open(`API "${api.name}" deleted successfully`, 'Close', {
399
- duration: 3000
400
- });
401
- this.loadAPIs();
402
- },
403
- error: (err) => {
404
- this.snackBar.open(err.error?.detail || 'Failed to delete API', 'Close', {
405
- duration: 5000,
406
- panelClass: 'error-snackbar'
407
- });
408
- }
409
- });
410
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  });
412
  }
413
 
@@ -422,7 +561,17 @@ export class ApisComponent implements OnInit {
422
 
423
  try {
424
  const text = await file.text();
425
- const apis = JSON.parse(text);
 
 
 
 
 
 
 
 
 
 
426
 
427
  if (!Array.isArray(apis)) {
428
  this.snackBar.open('Invalid file format. Expected an array of APIs.', 'Close', {
@@ -432,8 +581,10 @@ export class ApisComponent implements OnInit {
432
  return;
433
  }
434
 
 
435
  let imported = 0;
436
  let failed = 0;
 
437
 
438
  for (const api of apis) {
439
  try {
@@ -441,15 +592,33 @@ export class ApisComponent implements OnInit {
441
  imported++;
442
  } catch (err: any) {
443
  failed++;
444
- console.error(`Failed to import API ${api.name}:`, err);
 
 
 
445
  }
446
  }
447
 
448
- this.snackBar.open(`Imported ${imported} APIs. ${failed} failed.`, 'Close', {
449
- duration: 5000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
  });
451
- this.loadAPIs();
452
  } catch (error) {
 
453
  this.snackBar.open('Failed to read file', 'Close', {
454
  duration: 5000,
455
  panelClass: 'error-snackbar'
@@ -470,17 +639,42 @@ export class ApisComponent implements OnInit {
470
  return;
471
  }
472
 
473
- const data = JSON.stringify(selectedAPIs, null, 2);
474
- const blob = new Blob([data], { type: 'application/json' });
475
- const url = window.URL.createObjectURL(blob);
476
- const link = document.createElement('a');
477
- link.href = url;
478
- link.download = `apis_export_${new Date().getTime()}.json`;
479
- link.click();
480
- window.URL.revokeObjectURL(url);
 
 
 
 
 
 
 
 
 
 
 
 
 
481
 
482
- this.snackBar.open(`Exported ${selectedAPIs.length} APIs`, 'Close', {
483
- duration: 3000
484
- });
 
 
 
 
 
 
 
 
 
 
 
 
485
  }
486
  }
 
1
+ import { Component, inject, OnInit, OnDestroy } from '@angular/core';
2
  import { CommonModule } from '@angular/common';
3
  import { FormsModule } from '@angular/forms';
4
  import { MatDialog, MatDialogModule } from '@angular/material/dialog';
 
15
  import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
16
  import { MatDividerModule } from '@angular/material/divider';
17
  import { ApiService, API } from '../../services/api.service';
18
+ import { Subject, takeUntil } from 'rxjs';
19
 
20
  @Component({
21
  selector: 'app-apis',
 
42
  <div class="toolbar">
43
  <h2>API Definitions</h2>
44
  <div class="toolbar-actions">
45
+ <button mat-raised-button color="primary" (click)="createAPI()" [disabled]="loading">
46
  <mat-icon>add</mat-icon>
47
  New API
48
  </button>
49
+ <button mat-button (click)="importAPIs()" [disabled]="loading">
50
  <mat-icon>upload</mat-icon>
51
  Import
52
  </button>
53
+ <button mat-button (click)="exportAPIs()" [disabled]="loading || filteredAPIs.length === 0">
54
  <mat-icon>download</mat-icon>
55
  Export
56
  </button>
 
67
 
68
  <mat-progress-bar mode="indeterminate" *ngIf="loading"></mat-progress-bar>
69
 
70
+ @if (!loading && error) {
71
+ <div class="error-state">
72
+ <mat-icon>error_outline</mat-icon>
73
+ <p>{{ error }}</p>
74
+ <button mat-raised-button color="primary" (click)="loadAPIs()">
75
+ <mat-icon>refresh</mat-icon>
76
+ Retry
77
+ </button>
78
+ </div>
79
+ } @else if (!loading && filteredAPIs.length === 0 && !searchTerm) {
80
  <div class="empty-state">
81
  <mat-icon>api</mat-icon>
82
  <p>No APIs found.</p>
 
84
  Create your first API
85
  </button>
86
  </div>
87
+ } @else if (!loading && filteredAPIs.length === 0 && searchTerm) {
88
+ <div class="empty-state">
89
+ <mat-icon>search_off</mat-icon>
90
+ <p>No APIs match your search.</p>
91
+ <button mat-button (click)="searchTerm = ''; filterAPIs()">
92
+ Clear search
93
+ </button>
94
+ </div>
95
  } @else if (!loading) {
96
  <table mat-table [dataSource]="filteredAPIs" class="apis-table">
97
  <!-- Name Column -->
 
128
  <ng-container matColumnDef="auth">
129
  <th mat-header-cell *matHeaderCellDef>Auth</th>
130
  <td mat-cell *matCellDef="let api">
131
+ <mat-icon [color]="api.auth?.enabled ? 'primary' : ''"
132
+ [matTooltip]="api.auth?.enabled ? 'Authentication enabled' : 'No authentication'">
133
  {{ api.auth?.enabled ? 'lock' : 'lock_open' }}
134
  </mat-icon>
135
  </td>
 
137
 
138
  <!-- Deleted Column -->
139
  <ng-container matColumnDef="deleted">
140
+ <th mat-header-cell *matHeaderCellDef>Status</th>
141
  <td mat-cell *matCellDef="let api">
142
  @if (api.deleted) {
143
+ <mat-icon color="warn" matTooltip="Deleted">delete</mat-icon>
144
+ } @else {
145
+ <mat-icon color="primary" matTooltip="Active">check_circle</mat-icon>
146
  }
147
  </td>
148
  </ng-container>
 
151
  <ng-container matColumnDef="actions">
152
  <th mat-header-cell *matHeaderCellDef>Actions</th>
153
  <td mat-cell *matCellDef="let api">
154
+ <button mat-icon-button [matMenuTriggerFor]="menu"
155
+ (click)="$event.stopPropagation()"
156
+ [disabled]="actionLoading[api.name]">
157
+ @if (actionLoading[api.name]) {
158
+ <mat-spinner diameter="20"></mat-spinner>
159
+ } @else {
160
+ <mat-icon>more_vert</mat-icon>
161
+ }
162
  </button>
163
  <mat-menu #menu="matMenu">
164
+ <button mat-menu-item (click)="editAPI(api)" [disabled]="api.deleted">
165
  <mat-icon>edit</mat-icon>
166
  <span>Edit</span>
167
  </button>
 
179
  <mat-icon color="warn">delete</mat-icon>
180
  <span>Delete</span>
181
  </button>
182
+ } @else {
183
+ <mat-divider></mat-divider>
184
+ <button mat-menu-item (click)="restoreAPI(api)">
185
+ <mat-icon color="primary">restore</mat-icon>
186
+ <span>Restore</span>
187
+ </button>
188
  }
189
  </mat-menu>
190
  </td>
 
225
  }
226
  }
227
 
228
+ .empty-state, .error-state {
229
  text-align: center;
230
  padding: 60px 20px;
231
  background-color: white;
 
247
  }
248
  }
249
 
250
+ .error-state {
251
+ mat-icon {
252
+ color: #f44336;
253
+ }
254
+ }
255
+
256
  .apis-table {
257
  width: 100%;
258
  background: white;
 
292
  &.deleted-row {
293
  opacity: 0.6;
294
  background-color: #fafafa;
295
+ cursor: default;
296
  }
297
  }
298
+
299
+ mat-spinner {
300
+ display: inline-block;
301
+ }
302
  }
303
  }
304
 
 
315
  }
316
  `]
317
  })
318
+ export class ApisComponent implements OnInit, OnDestroy {
319
  private apiService = inject(ApiService);
320
  private dialog = inject(MatDialog);
321
  private snackBar = inject(MatSnackBar);
322
+ private destroyed$ = new Subject<void>();
323
 
324
  apis: API[] = [];
325
  filteredAPIs: API[] = [];
326
  loading = true;
327
+ error = '';
328
  showDeleted = false;
329
  searchTerm = '';
330
+ actionLoading: { [key: string]: boolean } = {};
331
 
332
  displayedColumns: string[] = ['name', 'url', 'method', 'timeout', 'auth', 'deleted', 'actions'];
333
 
 
335
  this.loadAPIs();
336
  }
337
 
338
+ ngOnDestroy() {
339
+ this.destroyed$.next();
340
+ this.destroyed$.complete();
341
+ }
342
+
343
  loadAPIs() {
344
  this.loading = true;
345
+ this.error = '';
346
+
347
+ this.apiService.getAPIs(this.showDeleted).pipe(
348
+ takeUntil(this.destroyed$)
349
+ ).subscribe({
350
  next: (apis) => {
351
  this.apis = apis;
352
  this.filterAPIs();
353
  this.loading = false;
354
  },
355
  error: (err) => {
356
+ this.error = this.getErrorMessage(err);
357
+ this.snackBar.open(this.error, 'Close', {
358
  duration: 5000,
359
  panelClass: 'error-snackbar'
360
  });
 
364
  }
365
 
366
  filterAPIs() {
367
+ const term = this.searchTerm.toLowerCase().trim();
368
+ if (!term) {
369
+ this.filteredAPIs = [...this.apis];
370
+ } else {
371
+ this.filteredAPIs = this.apis.filter(api =>
372
+ api.name.toLowerCase().includes(term) ||
373
+ api.url.toLowerCase().includes(term) ||
374
+ api.method.toLowerCase().includes(term)
375
+ );
376
+ }
377
  }
378
 
379
  async createAPI() {
380
+ try {
381
+ const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component');
382
+
383
+ const dialogRef = this.dialog.open(ApiEditDialogComponent, {
384
+ width: '800px',
385
+ data: { mode: 'create' },
386
+ disableClose: true
387
+ });
388
 
389
+ dialogRef.afterClosed().pipe(
390
+ takeUntil(this.destroyed$)
391
+ ).subscribe((result: any) => {
392
+ if (result) {
393
+ this.loadAPIs();
394
+ }
395
+ });
396
+ } catch (error) {
397
+ console.error('Failed to load dialog:', error);
398
+ this.snackBar.open('Failed to open dialog', 'Close', {
399
+ duration: 3000,
400
+ panelClass: 'error-snackbar'
401
+ });
402
+ }
403
  }
404
 
405
  async editAPI(api: API) {
406
+ if (api.deleted) return;
407
 
408
+ try {
409
+ const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component');
410
+
411
+ const dialogRef = this.dialog.open(ApiEditDialogComponent, {
412
+ width: '800px',
413
+ data: { mode: 'edit', api: { ...api } },
414
+ disableClose: true
415
+ });
416
 
417
+ dialogRef.afterClosed().pipe(
418
+ takeUntil(this.destroyed$)
419
+ ).subscribe((result: any) => {
420
+ if (result) {
421
+ this.loadAPIs();
422
+ }
423
+ });
424
+ } catch (error) {
425
+ console.error('Failed to load dialog:', error);
426
+ this.snackBar.open('Failed to open dialog', 'Close', {
427
+ duration: 3000,
428
+ panelClass: 'error-snackbar'
429
+ });
430
+ }
431
  }
432
 
433
  async testAPI(api: API) {
434
+ try {
435
+ const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component');
436
+
437
+ const dialogRef = this.dialog.open(ApiEditDialogComponent, {
438
+ width: '800px',
439
+ data: {
440
+ mode: 'test',
441
+ api: { ...api },
442
+ activeTab: 4
443
+ },
444
+ disableClose: false
445
+ });
446
 
447
+ dialogRef.afterClosed().pipe(
448
+ takeUntil(this.destroyed$)
449
+ ).subscribe((result: any) => {
450
+ if (result) {
451
+ this.loadAPIs();
452
+ }
453
+ });
454
+ } catch (error) {
455
+ console.error('Failed to load dialog:', error);
456
+ this.snackBar.open('Failed to open dialog', 'Close', {
457
+ duration: 3000,
458
+ panelClass: 'error-snackbar'
459
+ });
460
+ }
461
  }
462
 
463
  async duplicateAPI(api: API) {
464
+ try {
465
+ const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component');
466
+
467
+ const duplicatedApi = { ...api };
468
+ duplicatedApi.name = `${api.name}_copy`;
469
+ delete (duplicatedApi as any).last_update_date;
470
+
471
+ const dialogRef = this.dialog.open(ApiEditDialogComponent, {
472
+ width: '800px',
473
+ data: { mode: 'duplicate', api: duplicatedApi },
474
+ disableClose: true
475
+ });
476
 
477
+ dialogRef.afterClosed().pipe(
478
+ takeUntil(this.destroyed$)
479
+ ).subscribe((result: any) => {
480
+ if (result) {
481
+ this.loadAPIs();
482
+ }
483
+ });
484
+ } catch (error) {
485
+ console.error('Failed to load dialog:', error);
486
+ this.snackBar.open('Failed to open dialog', 'Close', {
487
+ duration: 3000,
488
+ panelClass: 'error-snackbar'
489
+ });
490
+ }
491
  }
492
 
493
  async deleteAPI(api: API) {
494
+ if (api.deleted) return;
495
 
496
+ try {
497
+ const { default: ConfirmDialogComponent } = await import('../../dialogs/confirm-dialog/confirm-dialog.component');
498
+
499
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
500
+ width: '400px',
501
+ data: {
502
+ title: 'Delete API',
503
+ message: `Are you sure you want to delete "${api.name}"?`,
504
+ confirmText: 'Delete',
505
+ confirmColor: 'warn'
506
+ }
507
+ });
508
 
509
+ dialogRef.afterClosed().pipe(
510
+ takeUntil(this.destroyed$)
511
+ ).subscribe((confirmed) => {
512
+ if (confirmed) {
513
+ this.actionLoading[api.name] = true;
514
+
515
+ this.apiService.deleteAPI(api.name).pipe(
516
+ takeUntil(this.destroyed$)
517
+ ).subscribe({
518
+ next: () => {
519
+ this.snackBar.open(`API "${api.name}" deleted successfully`, 'Close', {
520
+ duration: 3000
521
+ });
522
+ this.loadAPIs();
523
+ },
524
+ error: (err) => {
525
+ const errorMsg = this.getErrorMessage(err);
526
+ this.snackBar.open(errorMsg, 'Close', {
527
+ duration: 5000,
528
+ panelClass: 'error-snackbar'
529
+ });
530
+ this.actionLoading[api.name] = false;
531
+ }
532
+ });
533
+ }
534
+ });
535
+ } catch (error) {
536
+ console.error('Failed to load dialog:', error);
537
+ this.snackBar.open('Failed to open dialog', 'Close', {
538
+ duration: 3000,
539
+ panelClass: 'error-snackbar'
540
+ });
541
+ }
542
+ }
543
+
544
+ async restoreAPI(api: API) {
545
+ if (!api.deleted) return;
546
+
547
+ // Implement restore API functionality
548
+ this.snackBar.open('Restore functionality not implemented yet', 'Close', {
549
+ duration: 3000
550
  });
551
  }
552
 
 
561
 
562
  try {
563
  const text = await file.text();
564
+ let apis: any[];
565
+
566
+ try {
567
+ apis = JSON.parse(text);
568
+ } catch (parseError) {
569
+ this.snackBar.open('Invalid JSON file format', 'Close', {
570
+ duration: 5000,
571
+ panelClass: 'error-snackbar'
572
+ });
573
+ return;
574
+ }
575
 
576
  if (!Array.isArray(apis)) {
577
  this.snackBar.open('Invalid file format. Expected an array of APIs.', 'Close', {
 
581
  return;
582
  }
583
 
584
+ this.loading = true;
585
  let imported = 0;
586
  let failed = 0;
587
+ const errors: string[] = [];
588
 
589
  for (const api of apis) {
590
  try {
 
592
  imported++;
593
  } catch (err: any) {
594
  failed++;
595
+ const apiName = api.name || 'unnamed';
596
+ const errorMsg = err.error?.detail || err.message || 'Unknown error';
597
+ errors.push(`${apiName}: ${errorMsg}`);
598
+ console.error(`Failed to import API ${apiName}:`, err);
599
  }
600
  }
601
 
602
+ this.loading = false;
603
+
604
+ if (imported > 0) {
605
+ this.loadAPIs();
606
+ }
607
+
608
+ let message = `Imported ${imported} APIs.`;
609
+ if (failed > 0) {
610
+ message += ` ${failed} failed.`;
611
+ if (errors.length > 0 && errors.length <= 3) {
612
+ message += '\n' + errors.join('\n');
613
+ }
614
+ }
615
+
616
+ this.snackBar.open(message, 'Close', {
617
+ duration: failed > 0 ? 8000 : 5000,
618
+ panelClass: failed > 0 ? 'error-snackbar' : undefined
619
  });
 
620
  } catch (error) {
621
+ this.loading = false;
622
  this.snackBar.open('Failed to read file', 'Close', {
623
  duration: 5000,
624
  panelClass: 'error-snackbar'
 
639
  return;
640
  }
641
 
642
+ try {
643
+ const data = JSON.stringify(selectedAPIs, null, 2);
644
+ const blob = new Blob([data], { type: 'application/json' });
645
+ const url = window.URL.createObjectURL(blob);
646
+ const link = document.createElement('a');
647
+ link.href = url;
648
+ link.download = `apis_export_${new Date().getTime()}.json`;
649
+ link.click();
650
+ window.URL.revokeObjectURL(url);
651
+
652
+ this.snackBar.open(`Exported ${selectedAPIs.length} APIs`, 'Close', {
653
+ duration: 3000
654
+ });
655
+ } catch (error) {
656
+ console.error('Export failed:', error);
657
+ this.snackBar.open('Failed to export APIs', 'Close', {
658
+ duration: 5000,
659
+ panelClass: 'error-snackbar'
660
+ });
661
+ }
662
+ }
663
 
664
+ private getErrorMessage(error: any): string {
665
+ if (error.status === 0) {
666
+ return 'Unable to connect to server. Please check your connection.';
667
+ } else if (error.status === 401) {
668
+ return 'Session expired. Please login again.';
669
+ } else if (error.status === 403) {
670
+ return 'You do not have permission to perform this action.';
671
+ } else if (error.status === 409) {
672
+ return 'This API was modified by another user. Please refresh and try again.';
673
+ } else if (error.error?.detail) {
674
+ return error.error.detail;
675
+ } else if (error.message) {
676
+ return error.message;
677
+ }
678
+ return 'An unexpected error occurred. Please try again.';
679
  }
680
  }