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: `
@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.';
}
}