import { Component, inject, OnInit, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { MatTableModule } from '@angular/material/table'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatChipsModule } from '@angular/material/chips'; import { MatMenuModule } from '@angular/material/menu'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; import { MatDividerModule } from '@angular/material/divider'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { ApiService, API } from '../../services/api.service'; import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-apis', standalone: true, imports: [ CommonModule, FormsModule, MatDialogModule, MatTableModule, MatButtonModule, MatIconModule, MatFormFieldModule, MatInputModule, MatCheckboxModule, MatProgressBarModule, MatChipsModule, MatMenuModule, MatTooltipModule, MatSnackBarModule, MatDividerModule, MatProgressSpinnerModule ], template: `

API Definitions

Search APIs search Display Deleted
@if (!loading && error) {
error_outline

{{ error }}

} @else if (!loading && filteredAPIs.length === 0 && !searchTerm) {
api

No APIs found.

} @else if (!loading && filteredAPIs.length === 0 && searchTerm) {
search_off

No APIs match your search.

} @else if (!loading) {
Name {{ api.name }} URL {{ api.url }} Method {{ api.method }} Timeout {{ api.timeout_seconds }}s Auth {{ api.auth?.enabled ? 'lock' : 'lock_open' }} Status @if (api.deleted) { delete } @else { check_circle } Actions @if (!api.deleted) { } @else { }
}
`, styles: [` .apis-container { .toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 16px; h2 { margin: 0; font-size: 24px; } .toolbar-actions { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; .search-field { width: 250px; } } } .empty-state, .error-state { text-align: center; padding: 60px 20px; background-color: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); mat-icon { font-size: 64px; width: 64px; height: 64px; color: #e0e0e0; margin-bottom: 16px; } p { margin-bottom: 24px; color: #666; font-size: 16px; } } .error-state { mat-icon { color: #f44336; } } .apis-table { width: 100%; background: white; box-shadow: 0 2px 4px rgba(0,0,0,0.1); .url-cell { max-width: 300px; span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; } } mat-chip { font-size: 12px; min-height: 24px; padding: 4px 12px; &.method-get { background-color: #4caf50; color: white; } &.method-post { background-color: #2196f3; color: white; } &.method-put { background-color: #ff9800; color: white; } &.method-patch { background-color: #9c27b0; color: white; } &.method-delete { background-color: #f44336; color: white; } } tr.mat-mdc-row { cursor: pointer; transition: background-color 0.2s; &:hover { background-color: #f5f5f5; } &.deleted-row { opacity: 0.6; background-color: #fafafa; cursor: default; } } mat-spinner { display: inline-block; } } } ::ng-deep { .mat-mdc-form-field { font-size: 14px; } .mat-mdc-checkbox { .mdc-form-field { font-size: 14px; } } } `] }) export class ApisComponent implements OnInit, OnDestroy { private apiService = inject(ApiService); private dialog = inject(MatDialog); private snackBar = inject(MatSnackBar); private destroyed$ = new Subject(); apis: API[] = []; filteredAPIs: API[] = []; loading = true; error = ''; showDeleted = false; searchTerm = ''; actionLoading: { [key: string]: boolean } = {}; displayedColumns: string[] = ['name', 'url', 'method', 'timeout', 'auth', 'deleted', 'actions']; ngOnInit() { this.loadAPIs(); } ngOnDestroy() { this.destroyed$.next(); this.destroyed$.complete(); } loadAPIs() { this.loading = true; this.error = ''; this.apiService.getAPIs(this.showDeleted).pipe( takeUntil(this.destroyed$) ).subscribe({ next: (apis) => { this.apis = apis; this.filterAPIs(); this.loading = false; }, error: (err) => { this.error = this.getErrorMessage(err); this.snackBar.open(this.error, 'Close', { duration: 5000, panelClass: 'error-snackbar' }); this.loading = false; } }); } filterAPIs() { const term = this.searchTerm.toLowerCase().trim(); if (!term) { this.filteredAPIs = [...this.apis]; } else { this.filteredAPIs = this.apis.filter(api => api.name.toLowerCase().includes(term) || api.url.toLowerCase().includes(term) || api.method.toLowerCase().includes(term) ); } } async createAPI() { try { const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component'); const dialogRef = this.dialog.open(ApiEditDialogComponent, { width: '800px', data: { mode: 'create' }, disableClose: true }); dialogRef.afterClosed().pipe( takeUntil(this.destroyed$) ).subscribe((result: any) => { if (result) { this.loadAPIs(); } }); } catch (error) { console.error('Failed to load dialog:', error); this.snackBar.open('Failed to open dialog', 'Close', { duration: 3000, panelClass: 'error-snackbar' }); } } async editAPI(api: API) { if (api.deleted) return; try { const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component'); const dialogRef = this.dialog.open(ApiEditDialogComponent, { width: '800px', data: { mode: 'edit', api: { ...api } }, disableClose: true }); dialogRef.afterClosed().pipe( takeUntil(this.destroyed$) ).subscribe((result: any) => { if (result) { this.loadAPIs(); } }); } catch (error) { console.error('Failed to load dialog:', error); this.snackBar.open('Failed to open dialog', 'Close', { duration: 3000, panelClass: 'error-snackbar' }); } } async testAPI(api: API) { try { const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component'); const dialogRef = this.dialog.open(ApiEditDialogComponent, { width: '800px', data: { mode: 'test', api: { ...api }, activeTab: 4 }, disableClose: false }); dialogRef.afterClosed().pipe( takeUntil(this.destroyed$) ).subscribe((result: any) => { if (result) { this.loadAPIs(); } }); } catch (error) { console.error('Failed to load dialog:', error); this.snackBar.open('Failed to open dialog', 'Close', { duration: 3000, panelClass: 'error-snackbar' }); } } async duplicateAPI(api: API) { try { const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component'); const duplicatedApi = { ...api }; duplicatedApi.name = `${api.name}_copy`; delete (duplicatedApi as any).last_update_date; const dialogRef = this.dialog.open(ApiEditDialogComponent, { width: '800px', data: { mode: 'duplicate', api: duplicatedApi }, disableClose: true }); dialogRef.afterClosed().pipe( takeUntil(this.destroyed$) ).subscribe((result: any) => { if (result) { this.loadAPIs(); } }); } catch (error) { console.error('Failed to load dialog:', error); this.snackBar.open('Failed to open dialog', 'Close', { duration: 3000, panelClass: 'error-snackbar' }); } } async deleteAPI(api: API) { if (api.deleted) return; try { const { default: ConfirmDialogComponent } = await import('../../dialogs/confirm-dialog/confirm-dialog.component'); const dialogRef = this.dialog.open(ConfirmDialogComponent, { width: '400px', data: { title: 'Delete API', message: `Are you sure you want to delete "${api.name}"?`, confirmText: 'Delete', confirmColor: 'warn' } }); dialogRef.afterClosed().pipe( takeUntil(this.destroyed$) ).subscribe((confirmed) => { if (confirmed) { this.actionLoading[api.name] = true; this.apiService.deleteAPI(api.name).pipe( takeUntil(this.destroyed$) ).subscribe({ next: () => { this.snackBar.open(`API "${api.name}" deleted successfully`, 'Close', { duration: 3000 }); this.loadAPIs(); }, error: (err) => { const errorMsg = this.getErrorMessage(err); this.snackBar.open(errorMsg, 'Close', { duration: 5000, panelClass: 'error-snackbar' }); this.actionLoading[api.name] = false; } }); } }); } catch (error) { console.error('Failed to load dialog:', error); this.snackBar.open('Failed to open dialog', 'Close', { duration: 3000, panelClass: 'error-snackbar' }); } } async restoreAPI(api: API) { if (!api.deleted) return; // Implement restore API functionality this.snackBar.open('Restore functionality not implemented yet', 'Close', { duration: 3000 }); } async importAPIs() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = async (event: any) => { const file = event.target.files[0]; if (!file) return; try { const text = await file.text(); let apis: any[]; try { apis = JSON.parse(text); } catch (parseError) { this.snackBar.open('Invalid JSON file format', 'Close', { duration: 5000, panelClass: 'error-snackbar' }); return; } if (!Array.isArray(apis)) { this.snackBar.open('Invalid file format. Expected an array of APIs.', 'Close', { duration: 5000, panelClass: 'error-snackbar' }); return; } this.loading = true; let imported = 0; let failed = 0; const errors: string[] = []; console.log('Starting API import, total APIs:', apis.length); for (const api of apis) { try { await this.apiService.createAPI(api).toPromise(); imported++; } catch (err: any) { failed++; const apiName = api.name || 'unnamed'; console.error(`❌ Failed to import API ${apiName}:`, err); // Parse error message - daha iyi hata mesajı parse etme let errorMsg = 'Unknown error'; if (err.status === 409) { // DuplicateResourceError durumu errorMsg = `API with name '${apiName}' already exists`; } else if (err.status === 500 && err.error?.detail?.includes('already exists')) { // Backend'den gelen duplicate hatası errorMsg = `API with name '${apiName}' already exists`; } else if (err.error?.message) { errorMsg = err.error.message; } else if (err.error?.detail) { errorMsg = err.error.detail; } else if (err.message) { errorMsg = err.message; } errors.push(`${apiName}: ${errorMsg}`); } } this.loading = false; if (imported > 0) { this.loadAPIs(); } // Always show dialog for import results try { await this.showImportResultsDialog(imported, failed, errors); } catch (dialogError) { console.error('Failed to show import dialog:', dialogError); // Fallback to snackbar this.showImportResultsSnackbar(imported, failed, errors); } } catch (error) { this.loading = false; this.snackBar.open('Failed to read file', 'Close', { duration: 5000, panelClass: 'error-snackbar' }); } }; input.click(); } private async showImportResultsDialog(imported: number, failed: number, errors: string[]) { try { const { default: ImportResultsDialogComponent } = await import('../../dialogs/import-results-dialog/import-results-dialog.component'); this.dialog.open(ImportResultsDialogComponent, { width: '600px', data: { title: 'API Import Results', imported, failed, errors } }); } catch (error) { // Fallback to alert if dialog fails to load alert(`Imported: ${imported}\nFailed: ${failed}\n\nErrors:\n${errors.join('\n')}`); } } // Fallback method private showImportResultsSnackbar(imported: number, failed: number, errors: string[]) { let message = ''; if (imported > 0) { message = `Successfully imported ${imported} API${imported > 1 ? 's' : ''}.`; } if (failed > 0) { if (message) message += '\n\n'; message += `Failed to import ${failed} API${failed > 1 ? 's' : ''}:\n`; message += errors.slice(0, 5).join('\n'); if (errors.length > 5) { message += `\n... and ${errors.length - 5} more errors`; } } this.snackBar.open(message, 'Close', { duration: 10000, panelClass: ['multiline-snackbar', failed > 0 ? 'error-snackbar' : 'success-snackbar'], verticalPosition: 'top', horizontalPosition: 'right' }); } exportAPIs() { const selectedAPIs = this.filteredAPIs.filter(api => !api.deleted); if (selectedAPIs.length === 0) { this.snackBar.open('No APIs to export', 'Close', { duration: 3000 }); return; } try { const data = JSON.stringify(selectedAPIs, null, 2); const blob = new Blob([data], { type: 'application/json' }); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `apis_export_${new Date().getTime()}.json`; link.click(); window.URL.revokeObjectURL(url); this.snackBar.open(`Exported ${selectedAPIs.length} APIs`, 'Close', { duration: 3000 }); } catch (error) { console.error('Export failed:', error); this.snackBar.open('Failed to export APIs', 'Close', { duration: 5000, panelClass: 'error-snackbar' }); } } private getErrorMessage(error: any): string { if (error.status === 0) { return 'Unable to connect to server. Please check your connection.'; } else if (error.status === 401) { return 'Session expired. Please login again.'; } else if (error.status === 403) { return 'You do not have permission to perform this action.'; } else if (error.status === 409) { return 'This API was modified by another user. Please refresh and try again.'; } else if (error.error?.detail) { return error.error.detail; } else if (error.message) { return error.message; } return 'An unexpected error occurred. Please try again.'; } }