ciyidogan commited on
Commit
6907b62
·
verified ·
1 Parent(s): 97f2998

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

Browse files
flare-ui/src/app/components/apis/apis.component.ts CHANGED
@@ -2,120 +2,165 @@ 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';
 
 
 
 
 
 
 
 
 
 
 
5
  import { ApiService, API } from '../../services/api.service';
6
 
7
  @Component({
8
  selector: 'app-apis',
9
  standalone: true,
10
- imports: [CommonModule, FormsModule, MatDialogModule],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  template: `
12
  <div class="apis-container">
13
  <div class="toolbar">
14
  <h2>API Definitions</h2>
15
  <div class="toolbar-actions">
16
- <button class="btn btn-primary" (click)="createAPI()">
 
17
  New API
18
  </button>
19
- <button class="btn btn-secondary" (click)="importAPIs()">
 
20
  Import
21
  </button>
22
- <button class="btn btn-secondary" (click)="exportAPIs()">
 
23
  Export
24
  </button>
25
- <input
26
- type="text"
27
- placeholder="Search APIs..."
28
- [(ngModel)]="searchTerm"
29
- (input)="filterAPIs()"
30
- class="search-input"
31
- >
32
- <label class="checkbox-label">
33
- <input
34
- type="checkbox"
35
- [(ngModel)]="showDeleted"
36
- (change)="loadAPIs()"
37
- >
38
  Display Deleted
39
- </label>
40
  </div>
41
  </div>
42
 
43
- @if (loading) {
44
- <div class="loading">
45
- <span class="spinner"></span> Loading APIs...
46
- </div>
47
- } @else if (filteredAPIs.length === 0) {
48
  <div class="empty-state">
 
49
  <p>No APIs found.</p>
50
- <button class="btn btn-primary" (click)="createAPI()">
51
  Create your first API
52
  </button>
53
  </div>
54
- } @else {
55
- <table class="table">
56
- <thead>
57
- <tr>
58
- <th>Name</th>
59
- <th>URL</th>
60
- <th>Method</th>
61
- <th>Timeout</th>
62
- <th>Auth</th>
63
- <th>Deleted</th>
64
- <th>Actions</th>
65
- </tr>
66
- </thead>
67
- <tbody>
68
- @for (api of filteredAPIs; track api.name) {
69
- <tr [class.deleted]="api.deleted">
70
- <td>{{ api.name }}</td>
71
- <td class="url-cell">{{ api.url }}</td>
72
- <td>
73
- <span class="method-badge" [class]="'method-' + api.method.toLowerCase()">
74
- {{ api.method }}
75
- </span>
76
- </td>
77
- <td>{{ api.timeout_seconds }}s</td>
78
- <td>
79
- @if (api.auth?.enabled) {
80
- <span class="status-badge enabled">✓</span>
81
- } @else {
82
- <span class="status-badge">✗</span>
83
- }
84
- </td>
85
- <td>
86
- @if (api.deleted) {
87
- <span class="status-badge deleted">✓</span>
88
- } @else {
89
- <span class="status-badge">✗</span>
90
- }
91
- </td>
92
- <td class="actions">
93
- <button class="action-btn" title="Edit" (click)="editAPI(api)">
94
- 🖊️
95
- </button>
96
- <button class="action-btn" title="Test" (click)="testAPI(api)">
97
- 🧪
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  </button>
99
- <button class="action-btn" title="Duplicate" (click)="duplicateAPI(api)">
100
- 📋
101
- </button>
102
- @if (!api.deleted) {
103
- <button class="action-btn danger" title="Delete" (click)="deleteAPI(api)">
104
- 🗑️
105
- </button>
106
- }
107
- </td>
108
- </tr>
109
- }
110
- </tbody>
111
  </table>
112
  }
113
-
114
- @if (message) {
115
- <div class="alert" [class.alert-success]="!isError" [class.alert-danger]="isError">
116
- {{ message }}
117
- </div>
118
- }
119
  </div>
120
  `,
121
  styles: [`
@@ -124,95 +169,102 @@ import { ApiService, API } from '../../services/api.service';
124
  display: flex;
125
  justify-content: space-between;
126
  align-items: center;
127
- margin-bottom: 1.5rem;
 
 
128
 
129
  h2 {
130
  margin: 0;
 
131
  }
132
 
133
  .toolbar-actions {
134
  display: flex;
135
- gap: 0.5rem;
136
  align-items: center;
137
- }
138
- }
139
 
140
- .search-input {
141
- padding: 0.375rem 0.75rem;
142
- border: 1px solid #ced4da;
143
- border-radius: 0.25rem;
144
- width: 200px;
145
- }
146
-
147
- .checkbox-label {
148
- display: flex;
149
- align-items: center;
150
- gap: 0.25rem;
151
- cursor: pointer;
152
  }
153
 
154
- .loading, .empty-state {
155
  text-align: center;
156
- padding: 3rem;
157
  background-color: white;
158
- border-radius: 0.25rem;
 
 
 
 
 
 
 
 
 
159
 
160
  p {
161
- margin-bottom: 1rem;
162
- color: #6c757d;
 
163
  }
164
  }
165
 
166
- .url-cell {
167
- max-width: 300px;
168
- overflow: hidden;
169
- text-overflow: ellipsis;
170
- white-space: nowrap;
171
- }
 
 
 
 
 
 
 
 
 
172
 
173
- .method-badge {
174
- padding: 0.25rem 0.5rem;
175
- border-radius: 0.25rem;
176
- font-size: 0.75rem;
177
- font-weight: 600;
178
-
179
- &.method-get { background-color: #28a745; color: white; }
180
- &.method-post { background-color: #007bff; color: white; }
181
- &.method-put { background-color: #ffc107; color: #333; }
182
- &.method-patch { background-color: #17a2b8; color: white; }
183
- &.method-delete { background-color: #dc3545; color: white; }
184
- }
185
 
186
- .status-badge {
187
- &.enabled { color: #28a745; }
188
- &.deleted { color: #dc3545; }
189
- }
 
 
190
 
191
- .actions {
192
- display: flex;
193
- gap: 0.25rem;
194
- }
195
 
196
- .action-btn {
197
- background: none;
198
- border: none;
199
- cursor: pointer;
200
- font-size: 1.1rem;
201
- padding: 0.25rem;
202
- border-radius: 0.25rem;
203
 
204
- &:hover {
205
- background-color: #f8f9fa;
 
 
206
  }
 
 
207
 
208
- &.danger:hover {
209
- background-color: #f8d7da;
210
- }
211
  }
212
 
213
- tr.deleted {
214
- opacity: 0.6;
215
- background-color: #f8f9fa;
 
216
  }
217
  }
218
  `]
@@ -220,14 +272,15 @@ import { ApiService, API } from '../../services/api.service';
220
  export class ApisComponent implements OnInit {
221
  private apiService = inject(ApiService);
222
  private dialog = inject(MatDialog);
 
223
 
224
  apis: API[] = [];
225
  filteredAPIs: API[] = [];
226
  loading = true;
227
  showDeleted = false;
228
  searchTerm = '';
229
- message = '';
230
- isError = false;
231
 
232
  ngOnInit() {
233
  this.loadAPIs();
@@ -242,7 +295,10 @@ export class ApisComponent implements OnInit {
242
  this.loading = false;
243
  },
244
  error: (err) => {
245
- this.showMessage('Failed to load APIs', true);
 
 
 
246
  this.loading = false;
247
  }
248
  });
@@ -290,13 +346,21 @@ export class ApisComponent implements OnInit {
290
  this.apiService.testAPI(api).subscribe({
291
  next: (result) => {
292
  if (result.success) {
293
- this.showMessage(`API test successful! Status: ${result.status_code}`, false);
 
 
294
  } else {
295
- this.showMessage(`API test failed: ${result.error}`, true);
 
 
 
296
  }
297
  },
298
  error: (err) => {
299
- this.showMessage('Failed to test API', true);
 
 
 
300
  }
301
  });
302
  }
@@ -316,18 +380,37 @@ export class ApisComponent implements OnInit {
316
  });
317
  }
318
 
319
- deleteAPI(api: API) {
320
- if (confirm(`Are you sure you want to delete "${api.name}"?`)) {
321
- this.apiService.deleteAPI(api.name).subscribe({
322
- next: () => {
323
- this.showMessage(`API "${api.name}" deleted successfully`, false);
324
- this.loadAPIs();
325
- },
326
- error: (err) => {
327
- this.showMessage(err.error?.detail || 'Failed to delete API', true);
328
- }
329
- });
330
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  }
332
 
333
  importAPIs() {
@@ -344,7 +427,10 @@ export class ApisComponent implements OnInit {
344
  const apis = JSON.parse(text);
345
 
346
  if (!Array.isArray(apis)) {
347
- this.showMessage('Invalid file format. Expected an array of APIs.', true);
 
 
 
348
  return;
349
  }
350
 
@@ -361,10 +447,15 @@ export class ApisComponent implements OnInit {
361
  }
362
  }
363
 
364
- this.showMessage(`Imported ${imported} APIs. ${failed} failed.`, failed > 0);
 
 
365
  this.loadAPIs();
366
  } catch (error) {
367
- this.showMessage('Failed to read file', true);
 
 
 
368
  }
369
  };
370
 
@@ -375,7 +466,9 @@ export class ApisComponent implements OnInit {
375
  const selectedAPIs = this.filteredAPIs.filter(api => !api.deleted);
376
 
377
  if (selectedAPIs.length === 0) {
378
- this.showMessage('No APIs to export', true);
 
 
379
  return;
380
  }
381
 
@@ -388,15 +481,8 @@ export class ApisComponent implements OnInit {
388
  link.click();
389
  window.URL.revokeObjectURL(url);
390
 
391
- this.showMessage(`Exported ${selectedAPIs.length} APIs`, false);
392
- }
393
-
394
- private showMessage(message: string, isError: boolean) {
395
- this.message = message;
396
- this.isError = isError;
397
-
398
- setTimeout(() => {
399
- this.message = '';
400
- }, 5000);
401
  }
402
  }
 
2
  import { CommonModule } from '@angular/common';
3
  import { FormsModule } from '@angular/forms';
4
  import { MatDialog, MatDialogModule } from '@angular/material/dialog';
5
+ import { MatTableModule } from '@angular/material/table';
6
+ import { MatButtonModule } from '@angular/material/button';
7
+ import { MatIconModule } from '@angular/material/icon';
8
+ import { MatFormFieldModule } from '@angular/material/form-field';
9
+ import { MatInputModule } from '@angular/material/input';
10
+ import { MatCheckboxModule } from '@angular/material/checkbox';
11
+ import { MatProgressBarModule } from '@angular/material/progress-bar';
12
+ import { MatChipsModule } from '@angular/material/chips';
13
+ import { MatMenuModule } from '@angular/material/menu';
14
+ import { MatTooltipModule } from '@angular/material/tooltip';
15
+ import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
16
  import { ApiService, API } from '../../services/api.service';
17
 
18
  @Component({
19
  selector: 'app-apis',
20
  standalone: true,
21
+ imports: [
22
+ CommonModule,
23
+ FormsModule,
24
+ MatDialogModule,
25
+ MatTableModule,
26
+ MatButtonModule,
27
+ MatIconModule,
28
+ MatFormFieldModule,
29
+ MatInputModule,
30
+ MatCheckboxModule,
31
+ MatProgressBarModule,
32
+ MatChipsModule,
33
+ MatMenuModule,
34
+ MatTooltipModule,
35
+ MatSnackBarModule
36
+ ],
37
  template: `
38
  <div class="apis-container">
39
  <div class="toolbar">
40
  <h2>API Definitions</h2>
41
  <div class="toolbar-actions">
42
+ <button mat-raised-button color="primary" (click)="createAPI()">
43
+ <mat-icon>add</mat-icon>
44
  New API
45
  </button>
46
+ <button mat-button (click)="importAPIs()">
47
+ <mat-icon>upload</mat-icon>
48
  Import
49
  </button>
50
+ <button mat-button (click)="exportAPIs()">
51
+ <mat-icon>download</mat-icon>
52
  Export
53
  </button>
54
+ <mat-form-field appearance="outline" class="search-field">
55
+ <mat-label>Search APIs</mat-label>
56
+ <input matInput [(ngModel)]="searchTerm" (input)="filterAPIs()">
57
+ <mat-icon matSuffix>search</mat-icon>
58
+ </mat-form-field>
59
+ <mat-checkbox [(ngModel)]="showDeleted" (change)="loadAPIs()">
 
 
 
 
 
 
 
60
  Display Deleted
61
+ </mat-checkbox>
62
  </div>
63
  </div>
64
 
65
+ <mat-progress-bar mode="indeterminate" *ngIf="loading"></mat-progress-bar>
66
+
67
+ @if (!loading && filteredAPIs.length === 0) {
 
 
68
  <div class="empty-state">
69
+ <mat-icon>api</mat-icon>
70
  <p>No APIs found.</p>
71
+ <button mat-raised-button color="primary" (click)="createAPI()">
72
  Create your first API
73
  </button>
74
  </div>
75
+ } @else if (!loading) {
76
+ <table mat-table [dataSource]="filteredAPIs" class="apis-table">
77
+ <!-- Name Column -->
78
+ <ng-container matColumnDef="name">
79
+ <th mat-header-cell *matHeaderCellDef>Name</th>
80
+ <td mat-cell *matCellDef="let api">{{ api.name }}</td>
81
+ </ng-container>
82
+
83
+ <!-- URL Column -->
84
+ <ng-container matColumnDef="url">
85
+ <th mat-header-cell *matHeaderCellDef>URL</th>
86
+ <td mat-cell *matCellDef="let api" class="url-cell">
87
+ <span [matTooltip]="api.url">{{ api.url }}</span>
88
+ </td>
89
+ </ng-container>
90
+
91
+ <!-- Method Column -->
92
+ <ng-container matColumnDef="method">
93
+ <th mat-header-cell *matHeaderCellDef>Method</th>
94
+ <td mat-cell *matCellDef="let api">
95
+ <mat-chip [class]="'method-' + api.method.toLowerCase()">
96
+ {{ api.method }}
97
+ </mat-chip>
98
+ </td>
99
+ </ng-container>
100
+
101
+ <!-- Timeout Column -->
102
+ <ng-container matColumnDef="timeout">
103
+ <th mat-header-cell *matHeaderCellDef>Timeout</th>
104
+ <td mat-cell *matCellDef="let api">{{ api.timeout_seconds }}s</td>
105
+ </ng-container>
106
+
107
+ <!-- Auth Column -->
108
+ <ng-container matColumnDef="auth">
109
+ <th mat-header-cell *matHeaderCellDef>Auth</th>
110
+ <td mat-cell *matCellDef="let api">
111
+ <mat-icon [color]="api.auth?.enabled ? 'primary' : ''">
112
+ {{ api.auth?.enabled ? 'lock' : 'lock_open' }}
113
+ </mat-icon>
114
+ </td>
115
+ </ng-container>
116
+
117
+ <!-- Deleted Column -->
118
+ <ng-container matColumnDef="deleted">
119
+ <th mat-header-cell *matHeaderCellDef>Deleted</th>
120
+ <td mat-cell *matCellDef="let api">
121
+ @if (api.deleted) {
122
+ <mat-icon color="warn">delete</mat-icon>
123
+ }
124
+ </td>
125
+ </ng-container>
126
+
127
+ <!-- Actions Column -->
128
+ <ng-container matColumnDef="actions">
129
+ <th mat-header-cell *matHeaderCellDef>Actions</th>
130
+ <td mat-cell *matCellDef="let api">
131
+ <button mat-icon-button [matMenuTriggerFor]="menu" (click)="$event.stopPropagation()">
132
+ <mat-icon>more_vert</mat-icon>
133
+ </button>
134
+ <mat-menu #menu="matMenu">
135
+ <button mat-menu-item (click)="editAPI(api)">
136
+ <mat-icon>edit</mat-icon>
137
+ <span>Edit</span>
138
+ </button>
139
+ <button mat-menu-item (click)="testAPI(api)">
140
+ <mat-icon>play_arrow</mat-icon>
141
+ <span>Test</span>
142
+ </button>
143
+ <button mat-menu-item (click)="duplicateAPI(api)">
144
+ <mat-icon>content_copy</mat-icon>
145
+ <span>Duplicate</span>
146
+ </button>
147
+ @if (!api.deleted) {
148
+ <mat-divider></mat-divider>
149
+ <button mat-menu-item (click)="deleteAPI(api)">
150
+ <mat-icon color="warn">delete</mat-icon>
151
+ <span>Delete</span>
152
  </button>
153
+ }
154
+ </mat-menu>
155
+ </td>
156
+ </ng-container>
157
+
158
+ <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
159
+ <tr mat-row *matRowDef="let row; columns: displayedColumns;"
160
+ [class.deleted-row]="row.deleted"
161
+ (click)="editAPI(row)"></tr>
 
 
 
162
  </table>
163
  }
 
 
 
 
 
 
164
  </div>
165
  `,
166
  styles: [`
 
169
  display: flex;
170
  justify-content: space-between;
171
  align-items: center;
172
+ margin-bottom: 24px;
173
+ flex-wrap: wrap;
174
+ gap: 16px;
175
 
176
  h2 {
177
  margin: 0;
178
+ font-size: 24px;
179
  }
180
 
181
  .toolbar-actions {
182
  display: flex;
183
+ gap: 16px;
184
  align-items: center;
185
+ flex-wrap: wrap;
 
186
 
187
+ .search-field {
188
+ width: 250px;
189
+ }
190
+ }
 
 
 
 
 
 
 
 
191
  }
192
 
193
+ .empty-state {
194
  text-align: center;
195
+ padding: 60px 20px;
196
  background-color: white;
197
+ border-radius: 8px;
198
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
199
+
200
+ mat-icon {
201
+ font-size: 64px;
202
+ width: 64px;
203
+ height: 64px;
204
+ color: #e0e0e0;
205
+ margin-bottom: 16px;
206
+ }
207
 
208
  p {
209
+ margin-bottom: 24px;
210
+ color: #666;
211
+ font-size: 16px;
212
  }
213
  }
214
 
215
+ .apis-table {
216
+ width: 100%;
217
+ background: white;
218
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
219
+
220
+ .url-cell {
221
+ max-width: 300px;
222
+
223
+ span {
224
+ overflow: hidden;
225
+ text-overflow: ellipsis;
226
+ white-space: nowrap;
227
+ display: block;
228
+ }
229
+ }
230
 
231
+ mat-chip {
232
+ font-size: 12px;
233
+ min-height: 24px;
234
+ padding: 4px 12px;
 
 
 
 
 
 
 
 
235
 
236
+ &.method-get { background-color: #4caf50; color: white; }
237
+ &.method-post { background-color: #2196f3; color: white; }
238
+ &.method-put { background-color: #ff9800; color: white; }
239
+ &.method-patch { background-color: #9c27b0; color: white; }
240
+ &.method-delete { background-color: #f44336; color: white; }
241
+ }
242
 
243
+ tr.mat-mdc-row {
244
+ cursor: pointer;
245
+ transition: background-color 0.2s;
 
246
 
247
+ &:hover {
248
+ background-color: #f5f5f5;
249
+ }
 
 
 
 
250
 
251
+ &.deleted-row {
252
+ opacity: 0.6;
253
+ background-color: #fafafa;
254
+ }
255
  }
256
+ }
257
+ }
258
 
259
+ ::ng-deep {
260
+ .mat-mdc-form-field {
261
+ font-size: 14px;
262
  }
263
 
264
+ .mat-mdc-checkbox {
265
+ .mdc-form-field {
266
+ font-size: 14px;
267
+ }
268
  }
269
  }
270
  `]
 
272
  export class ApisComponent implements OnInit {
273
  private apiService = inject(ApiService);
274
  private dialog = inject(MatDialog);
275
+ private snackBar = inject(MatSnackBar);
276
 
277
  apis: API[] = [];
278
  filteredAPIs: API[] = [];
279
  loading = true;
280
  showDeleted = false;
281
  searchTerm = '';
282
+
283
+ displayedColumns: string[] = ['name', 'url', 'method', 'timeout', 'auth', 'deleted', 'actions'];
284
 
285
  ngOnInit() {
286
  this.loadAPIs();
 
295
  this.loading = false;
296
  },
297
  error: (err) => {
298
+ this.snackBar.open('Failed to load APIs', 'Close', {
299
+ duration: 5000,
300
+ panelClass: 'error-snackbar'
301
+ });
302
  this.loading = false;
303
  }
304
  });
 
346
  this.apiService.testAPI(api).subscribe({
347
  next: (result) => {
348
  if (result.success) {
349
+ this.snackBar.open(`API test successful! Status: ${result.status_code}`, 'Close', {
350
+ duration: 3000
351
+ });
352
  } else {
353
+ this.snackBar.open(`API test failed: ${result.error}`, 'Close', {
354
+ duration: 5000,
355
+ panelClass: 'error-snackbar'
356
+ });
357
  }
358
  },
359
  error: (err) => {
360
+ this.snackBar.open('Failed to test API', 'Close', {
361
+ duration: 5000,
362
+ panelClass: 'error-snackbar'
363
+ });
364
  }
365
  });
366
  }
 
380
  });
381
  }
382
 
383
+ async deleteAPI(api: API) {
384
+ const { default: ConfirmDialogComponent } = await import('../../dialogs/confirm-dialog/confirm-dialog.component');
385
+
386
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
387
+ width: '400px',
388
+ data: {
389
+ title: 'Delete API',
390
+ message: `Are you sure you want to delete "${api.name}"?`,
391
+ confirmText: 'Delete',
392
+ confirmColor: 'warn'
393
+ }
394
+ });
395
+
396
+ dialogRef.afterClosed().subscribe((confirmed) => {
397
+ if (confirmed) {
398
+ this.apiService.deleteAPI(api.name).subscribe({
399
+ next: () => {
400
+ this.snackBar.open(`API "${api.name}" deleted successfully`, 'Close', {
401
+ duration: 3000
402
+ });
403
+ this.loadAPIs();
404
+ },
405
+ error: (err) => {
406
+ this.snackBar.open(err.error?.detail || 'Failed to delete API', 'Close', {
407
+ duration: 5000,
408
+ panelClass: 'error-snackbar'
409
+ });
410
+ }
411
+ });
412
+ }
413
+ });
414
  }
415
 
416
  importAPIs() {
 
427
  const apis = JSON.parse(text);
428
 
429
  if (!Array.isArray(apis)) {
430
+ this.snackBar.open('Invalid file format. Expected an array of APIs.', 'Close', {
431
+ duration: 5000,
432
+ panelClass: 'error-snackbar'
433
+ });
434
  return;
435
  }
436
 
 
447
  }
448
  }
449
 
450
+ this.snackBar.open(`Imported ${imported} APIs. ${failed} failed.`, 'Close', {
451
+ duration: 5000
452
+ });
453
  this.loadAPIs();
454
  } catch (error) {
455
+ this.snackBar.open('Failed to read file', 'Close', {
456
+ duration: 5000,
457
+ panelClass: 'error-snackbar'
458
+ });
459
  }
460
  };
461
 
 
466
  const selectedAPIs = this.filteredAPIs.filter(api => !api.deleted);
467
 
468
  if (selectedAPIs.length === 0) {
469
+ this.snackBar.open('No APIs to export', 'Close', {
470
+ duration: 3000
471
+ });
472
  return;
473
  }
474
 
 
481
  link.click();
482
  window.URL.revokeObjectURL(url);
483
 
484
+ this.snackBar.open(`Exported ${selectedAPIs.length} APIs`, 'Close', {
485
+ duration: 3000
486
+ });
 
 
 
 
 
 
 
487
  }
488
  }