Spaces:
Paused
Paused
| import { Component, inject, OnInit } 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 { ApiService, API } from '../../services/api.service'; | |
| ({ | |
| selector: 'app-apis', | |
| standalone: true, | |
| imports: [ | |
| CommonModule, | |
| FormsModule, | |
| MatDialogModule, | |
| MatTableModule, | |
| MatButtonModule, | |
| MatIconModule, | |
| MatFormFieldModule, | |
| MatInputModule, | |
| MatCheckboxModule, | |
| MatProgressBarModule, | |
| MatChipsModule, | |
| MatMenuModule, | |
| MatTooltipModule, | |
| MatSnackBarModule, | |
| MatDividerModule | |
| ], | |
| template: ` | |
| <div class="apis-container"> | |
| <div class="toolbar"> | |
| <h2>API Definitions</h2> | |
| <div class="toolbar-actions"> | |
| <button mat-raised-button color="primary" (click)="createAPI()"> | |
| <mat-icon>add</mat-icon> | |
| New API | |
| </button> | |
| <button mat-button (click)="importAPIs()"> | |
| <mat-icon>upload</mat-icon> | |
| Import | |
| </button> | |
| <button mat-button (click)="exportAPIs()"> | |
| <mat-icon>download</mat-icon> | |
| Export | |
| </button> | |
| <mat-form-field appearance="outline" class="search-field"> | |
| <mat-label>Search APIs</mat-label> | |
| <input matInput [(ngModel)]="searchTerm" (input)="filterAPIs()"> | |
| <mat-icon matSuffix>search</mat-icon> | |
| </mat-form-field> | |
| <mat-checkbox [(ngModel)]="showDeleted" (change)="loadAPIs()"> | |
| Display Deleted | |
| </mat-checkbox> | |
| </div> | |
| </div> | |
| <mat-progress-bar mode="indeterminate" *ngIf="loading"></mat-progress-bar> | |
| @if (!loading && filteredAPIs.length === 0) { | |
| <div class="empty-state"> | |
| <mat-icon>api</mat-icon> | |
| <p>No APIs found.</p> | |
| <button mat-raised-button color="primary" (click)="createAPI()"> | |
| Create your first API | |
| </button> | |
| </div> | |
| } @else if (!loading) { | |
| <table mat-table [dataSource]="filteredAPIs" class="apis-table"> | |
| <!-- Name Column --> | |
| <ng-container matColumnDef="name"> | |
| <th mat-header-cell *matHeaderCellDef>Name</th> | |
| <td mat-cell *matCellDef="let api">{{ api.name }}</td> | |
| </ng-container> | |
| <!-- URL Column --> | |
| <ng-container matColumnDef="url"> | |
| <th mat-header-cell *matHeaderCellDef>URL</th> | |
| <td mat-cell *matCellDef="let api" class="url-cell"> | |
| <span [matTooltip]="api.url">{{ api.url }}</span> | |
| </td> | |
| </ng-container> | |
| <!-- Method Column --> | |
| <ng-container matColumnDef="method"> | |
| <th mat-header-cell *matHeaderCellDef>Method</th> | |
| <td mat-cell *matCellDef="let api"> | |
| <mat-chip [class]="'method-' + api.method.toLowerCase()"> | |
| {{ api.method }} | |
| </mat-chip> | |
| </td> | |
| </ng-container> | |
| <!-- Timeout Column --> | |
| <ng-container matColumnDef="timeout"> | |
| <th mat-header-cell *matHeaderCellDef>Timeout</th> | |
| <td mat-cell *matCellDef="let api">{{ api.timeout_seconds }}s</td> | |
| </ng-container> | |
| <!-- Auth Column --> | |
| <ng-container matColumnDef="auth"> | |
| <th mat-header-cell *matHeaderCellDef>Auth</th> | |
| <td mat-cell *matCellDef="let api"> | |
| <mat-icon [color]="api.auth?.enabled ? 'primary' : ''"> | |
| {{ api.auth?.enabled ? 'lock' : 'lock_open' }} | |
| </mat-icon> | |
| </td> | |
| </ng-container> | |
| <!-- Deleted Column --> | |
| <ng-container matColumnDef="deleted"> | |
| <th mat-header-cell *matHeaderCellDef>Deleted</th> | |
| <td mat-cell *matCellDef="let api"> | |
| @if (api.deleted) { | |
| <mat-icon color="warn">delete</mat-icon> | |
| } | |
| </td> | |
| </ng-container> | |
| <!-- Actions Column --> | |
| <ng-container matColumnDef="actions"> | |
| <th mat-header-cell *matHeaderCellDef>Actions</th> | |
| <td mat-cell *matCellDef="let api"> | |
| <button mat-icon-button [matMenuTriggerFor]="menu" (click)="$event.stopPropagation()"> | |
| <mat-icon>more_vert</mat-icon> | |
| </button> | |
| <mat-menu #menu="matMenu"> | |
| <button mat-menu-item (click)="editAPI(api)"> | |
| <mat-icon>edit</mat-icon> | |
| <span>Edit</span> | |
| </button> | |
| <button mat-menu-item (click)="testAPI(api)"> | |
| <mat-icon>play_arrow</mat-icon> | |
| <span>Test</span> | |
| </button> | |
| <button mat-menu-item (click)="duplicateAPI(api)"> | |
| <mat-icon>content_copy</mat-icon> | |
| <span>Duplicate</span> | |
| </button> | |
| @if (!api.deleted) { | |
| <mat-divider></mat-divider> | |
| <button mat-menu-item (click)="deleteAPI(api)"> | |
| <mat-icon color="warn">delete</mat-icon> | |
| <span>Delete</span> | |
| </button> | |
| } | |
| </mat-menu> | |
| </td> | |
| </ng-container> | |
| <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> | |
| <tr mat-row *matRowDef="let row; columns: displayedColumns;" | |
| [class.deleted-row]="row.deleted" | |
| (click)="editAPI(row)"></tr> | |
| </table> | |
| } | |
| </div> | |
| `, | |
| 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 { | |
| 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; | |
| } | |
| } | |
| .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; | |
| } | |
| } | |
| } | |
| } | |
| ::ng-deep { | |
| .mat-mdc-form-field { | |
| font-size: 14px; | |
| } | |
| .mat-mdc-checkbox { | |
| .mdc-form-field { | |
| font-size: 14px; | |
| } | |
| } | |
| } | |
| `] | |
| }) | |
| export class ApisComponent implements OnInit { | |
| private apiService = inject(ApiService); | |
| private dialog = inject(MatDialog); | |
| private snackBar = inject(MatSnackBar); | |
| apis: API[] = []; | |
| filteredAPIs: API[] = []; | |
| loading = true; | |
| showDeleted = false; | |
| searchTerm = ''; | |
| displayedColumns: string[] = ['name', 'url', 'method', 'timeout', 'auth', 'deleted', 'actions']; | |
| ngOnInit() { | |
| this.loadAPIs(); | |
| } | |
| loadAPIs() { | |
| this.loading = true; | |
| this.apiService.getAPIs(this.showDeleted).subscribe({ | |
| next: (apis) => { | |
| this.apis = apis; | |
| this.filterAPIs(); | |
| this.loading = false; | |
| }, | |
| error: (err) => { | |
| this.snackBar.open('Failed to load APIs', 'Close', { | |
| duration: 5000, | |
| panelClass: 'error-snackbar' | |
| }); | |
| this.loading = false; | |
| } | |
| }); | |
| } | |
| filterAPIs() { | |
| const term = this.searchTerm.toLowerCase(); | |
| this.filteredAPIs = this.apis.filter(api => | |
| api.name.toLowerCase().includes(term) || | |
| api.url.toLowerCase().includes(term) | |
| ); | |
| } | |
| async createAPI() { | |
| const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component'); | |
| const dialogRef = this.dialog.open(ApiEditDialogComponent, { | |
| width: '800px', | |
| data: { mode: 'create' } | |
| }); | |
| dialogRef.afterClosed().subscribe((result: any) => { | |
| if (result) { | |
| this.loadAPIs(); | |
| } | |
| }); | |
| } | |
| async editAPI(api: API) { | |
| 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 } } | |
| }); | |
| dialogRef.afterClosed().subscribe((result: any) => { | |
| if (result) { | |
| this.loadAPIs(); | |
| } | |
| }); | |
| } | |
| async testAPI(api: API) { | |
| const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component'); | |
| const dialogRef = this.dialog.open(ApiEditDialogComponent, { | |
| width: '800px', | |
| data: { | |
| mode: 'test', // Test modunda açıyoruz | |
| api: { ...api }, | |
| activeTab: 4 // Test tab'ını aktif yap (0-based index) | |
| } | |
| }); | |
| dialogRef.afterClosed().subscribe((result: any) => { | |
| if (result) { | |
| this.loadAPIs(); | |
| } | |
| }); | |
| } | |
| async duplicateAPI(api: API) { | |
| const { default: ApiEditDialogComponent } = await import('../../dialogs/api-edit-dialog/api-edit-dialog.component'); | |
| const dialogRef = this.dialog.open(ApiEditDialogComponent, { | |
| width: '800px', | |
| data: { mode: 'duplicate', api: { ...api } } | |
| }); | |
| dialogRef.afterClosed().subscribe((result: any) => { | |
| if (result) { | |
| this.loadAPIs(); | |
| } | |
| }); | |
| } | |
| async deleteAPI(api: API) { | |
| 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().subscribe((confirmed) => { | |
| if (confirmed) { | |
| this.apiService.deleteAPI(api.name).subscribe({ | |
| next: () => { | |
| this.snackBar.open(`API "${api.name}" deleted successfully`, 'Close', { | |
| duration: 3000 | |
| }); | |
| this.loadAPIs(); | |
| }, | |
| error: (err) => { | |
| this.snackBar.open(err.error?.detail || 'Failed to delete API', 'Close', { | |
| duration: 5000, | |
| panelClass: 'error-snackbar' | |
| }); | |
| } | |
| }); | |
| } | |
| }); | |
| } | |
| 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(); | |
| const apis = JSON.parse(text); | |
| if (!Array.isArray(apis)) { | |
| this.snackBar.open('Invalid file format. Expected an array of APIs.', 'Close', { | |
| duration: 5000, | |
| panelClass: 'error-snackbar' | |
| }); | |
| return; | |
| } | |
| let imported = 0; | |
| let failed = 0; | |
| for (const api of apis) { | |
| try { | |
| await this.apiService.createAPI(api).toPromise(); | |
| imported++; | |
| } catch (err: any) { | |
| failed++; | |
| console.error(`Failed to import API ${api.name}:`, err); | |
| } | |
| } | |
| this.snackBar.open(`Imported ${imported} APIs. ${failed} failed.`, 'Close', { | |
| duration: 5000 | |
| }); | |
| this.loadAPIs(); | |
| } catch (error) { | |
| this.snackBar.open('Failed to read file', 'Close', { | |
| duration: 5000, | |
| panelClass: 'error-snackbar' | |
| }); | |
| } | |
| }; | |
| input.click(); | |
| } | |
| exportAPIs() { | |
| const selectedAPIs = this.filteredAPIs.filter(api => !api.deleted); | |
| if (selectedAPIs.length === 0) { | |
| this.snackBar.open('No APIs to export', 'Close', { | |
| duration: 3000 | |
| }); | |
| return; | |
| } | |
| 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 | |
| }); | |
| } | |
| } |