Spaces:
Building
Building
<h2 mat-dialog-title> | |
@if (data.mode === 'create') { | |
Create New API | |
} @else if (data.mode === 'duplicate') { | |
Duplicate API | |
} @else if (data.mode === 'test') { | |
Test API: {{ data.api.name }} | |
} @else { | |
Edit API: {{ data.api.name }} | |
} | |
</h2> | |
<mat-dialog-content> | |
<mat-tab-group [(selectedIndex)]="activeTabIndex"> | |
<!-- General Tab --> | |
<mat-tab label="General"> | |
<div class="tab-content"> | |
<mat-form-field appearance="outline"> | |
<mat-label>Name</mat-label> | |
<input matInput [formControl]="$any(form.get('name'))" placeholder="e.g., get_flights"> | |
<mat-hint>Unique identifier for this API</mat-hint> | |
@if (form.get('name')?.hasError('required') && form.get('name')?.touched) { | |
<mat-error>Name is required</mat-error> | |
} | |
@if (form.get('name')?.hasError('pattern') && form.get('name')?.touched) { | |
<mat-error>Only alphanumeric and underscore allowed</mat-error> | |
} | |
</mat-form-field> | |
<mat-form-field appearance="outline"> | |
<mat-label>URL</mat-label> | |
<input matInput [formControl]="$any(form.get('url'))" placeholder="https://api.example.com/endpoint"> | |
<mat-hint>Full URL including protocol</mat-hint> | |
@if (form.get('url')?.hasError('required') && form.get('url')?.touched) { | |
<mat-error>URL is required</mat-error> | |
} | |
@if (form.get('url')?.hasError('pattern') && form.get('url')?.touched) { | |
<mat-error>Invalid URL format</mat-error> | |
} | |
</mat-form-field> | |
<div class="row"> | |
<mat-form-field appearance="outline" class="method-field"> | |
<mat-label>Method</mat-label> | |
<mat-select [formControl]="$any(form.get('method'))"> | |
@for (method of httpMethods; track method) { | |
<mat-option [value]="method">{{ method }}</mat-option> | |
} | |
</mat-select> | |
</mat-form-field> | |
<mat-form-field appearance="outline" class="timeout-field"> | |
<mat-label>Timeout (seconds)</mat-label> | |
<input matInput type="number" [formControl]="$any(form.get('timeout_seconds'))"> | |
<mat-hint>Request timeout in seconds</mat-hint> | |
@if (form.get('timeout_seconds')?.hasError('min')) { | |
<mat-error>Minimum 1 second</mat-error> | |
} | |
@if (form.get('timeout_seconds')?.hasError('max')) { | |
<mat-error>Maximum 300 seconds</mat-error> | |
} | |
</mat-form-field> | |
</div> | |
<app-json-editor | |
[formControl]="$any(form.get('body_template'))" | |
label="Body Template" | |
placeholder='{"key": "value"}' | |
hint="JSON template with template variable support" | |
[rows]="8" | |
[availableVariables]="getTemplateVariables(false)" | |
[variableReplacer]="replaceVariablesForValidation"> | |
</app-json-editor> | |
<mat-form-field appearance="outline"> | |
<mat-label>Proxy URL (Optional)</mat-label> | |
<input matInput [formControl]="$any(form.get('proxy'))" placeholder="http://proxy.example.com:8080"> | |
<mat-hint>HTTP proxy for this API call</mat-hint> | |
</mat-form-field> | |
</div> | |
</mat-tab> | |
<!-- Headers Tab --> | |
<mat-tab label="Headers"> | |
<div class="tab-content"> | |
<div class="array-section"> | |
<div class="section-header"> | |
<h3>Request Headers</h3> | |
<button mat-button color="primary" (click)="addHeader()"> | |
<mat-icon>add</mat-icon> | |
Add Header | |
</button> | |
</div> | |
@if (headers.length === 0) { | |
<p class="empty-message">No headers configured. Click "Add Header" to add one.</p> | |
} | |
@for (header of headers.controls; track header; let i = $index) { | |
<div class="array-item" [formGroup]="$any(header)"> | |
<mat-form-field appearance="outline" class="key-field"> | |
<mat-label>Header Name</mat-label> | |
<input matInput formControlName="key" placeholder="Content-Type"> | |
</mat-form-field> | |
<mat-form-field appearance="outline" class="value-field"> | |
<mat-label>Header Value</mat-label> | |
<input matInput formControlName="value" placeholder="application/json"> | |
<button mat-icon-button matSuffix [matMenuTriggerFor]="headerMenu"> | |
<mat-icon>code</mat-icon> | |
</button> | |
<mat-menu #headerMenu="matMenu"> | |
@for (variable of getTemplateVariables(); track variable) { | |
<button mat-menu-item (click)="insertHeaderValue(i, variable)"> | |
{{ variable }} | |
</button> | |
} | |
</mat-menu> | |
</mat-form-field> | |
<button mat-icon-button color="warn" (click)="removeHeader(i)"> | |
<mat-icon>delete</mat-icon> | |
</button> | |
</div> | |
} | |
</div> | |
</div> | |
</mat-tab> | |
<!-- Auth Tab --> | |
<mat-tab label="Authentication"> | |
<div class="tab-content" [formGroup]="$any(form.get('auth'))"> | |
<mat-checkbox formControlName="enabled"> | |
Enable Authentication | |
</mat-checkbox> | |
@if (form.get('auth.enabled')?.value) { | |
<mat-divider></mat-divider> | |
<div class="auth-section"> | |
<h3>Token Configuration</h3> | |
<mat-form-field appearance="outline"> | |
<mat-label>Token Endpoint</mat-label> | |
<input matInput formControlName="token_endpoint" placeholder="https://api.example.com/auth/token"> | |
<mat-hint>URL to obtain authentication token</mat-hint> | |
@if (form.get('auth.token_endpoint')?.hasError('required') && form.get('auth.token_endpoint')?.touched) { | |
<mat-error>Token endpoint is required when auth is enabled</mat-error> | |
} | |
</mat-form-field> | |
<mat-form-field appearance="outline"> | |
<mat-label>Token Response Path</mat-label> | |
<input matInput formControlName="response_token_path" placeholder="token"> | |
<mat-hint>JSON path to extract token from response</mat-hint> | |
@if (form.get('auth.response_token_path')?.hasError('required') && form.get('auth.response_token_path')?.touched) { | |
<mat-error>Token path is required when auth is enabled</mat-error> | |
} | |
</mat-form-field> | |
<app-json-editor | |
formControlName="token_request_body" | |
label="Token Request Body" | |
placeholder='{"username": "api_user", "password": "api_pass"}' | |
hint="JSON body for token request" | |
[rows]="6" | |
[availableVariables]="getTemplateVariables()" | |
[variableReplacer]="replaceVariablesForValidation"> | |
</app-json-editor> | |
<mat-divider></mat-divider> | |
<h3>Token Refresh (Optional)</h3> | |
<mat-form-field appearance="outline"> | |
<mat-label>Refresh Endpoint</mat-label> | |
<input matInput formControlName="token_refresh_endpoint" placeholder="https://api.example.com/auth/refresh"> | |
<mat-hint>URL to refresh expired token</mat-hint> | |
</mat-form-field> | |
<app-json-editor | |
formControlName="token_refresh_body" | |
label="Refresh Request Body" | |
placeholder='{"refresh_token": "your_refresh_token"}' | |
hint="JSON body for refresh request" | |
[rows]="4" | |
[availableVariables]="getTemplateVariables()" | |
[variableReplacer]="replaceVariablesForValidation"> | |
</app-json-editor> | |
</div> | |
} | |
</div> | |
</mat-tab> | |
<!-- Response Tab --> | |
<mat-tab label="Response"> | |
<div class="tab-content"> | |
<mat-form-field appearance="outline" class="full-width"> | |
<mat-label>Response Prompt</mat-label> | |
<textarea matInput | |
[formControl]="$any(form.get('response_prompt'))" | |
rows="4" | |
placeholder="Optional instructions for processing the response"></textarea> | |
<mat-hint>Instructions for AI to process the response (optional)</mat-hint> | |
</mat-form-field> | |
<mat-divider></mat-divider> | |
<div class="array-section"> | |
<div class="section-header"> | |
<h3>Response Mappings</h3> | |
<button mat-button color="primary" (click)="addResponseMapping()"> | |
<mat-icon>add</mat-icon> | |
Add Mapping | |
</button> | |
</div> | |
@if (responseMappings.length === 0) { | |
<p class="empty-message">No response mappings configured.</p> | |
} | |
@for (mapping of responseMappings.controls; track mapping; let i = $index) { | |
<mat-expansion-panel [formGroup]="$any(mapping)"> | |
<mat-expansion-panel-header> | |
<mat-panel-title> | |
{{ mapping.get('variable_name')?.value || 'New Mapping' }} | |
</mat-panel-title> | |
<mat-panel-description> | |
{{ mapping.get('json_path')?.value || 'Configure mapping' }} | |
</mat-panel-description> | |
</mat-expansion-panel-header> | |
<div class="mapping-content"> | |
<mat-form-field appearance="outline"> | |
<mat-label>Variable Name</mat-label> | |
<input matInput formControlName="variable_name" placeholder="booking_ref"> | |
<mat-hint>Name to store the extracted value</mat-hint> | |
@if (mapping.get('variable_name')?.hasError('pattern')) { | |
<mat-error>Lowercase letters, numbers and underscore only</mat-error> | |
} | |
</mat-form-field> | |
<mat-form-field appearance="outline"> | |
<mat-label>Caption</mat-label> | |
<input matInput formControlName="caption" placeholder="Booking Reference"> | |
<mat-hint>Human-readable description</mat-hint> | |
</mat-form-field> | |
<div class="row"> | |
<mat-form-field appearance="outline" class="type-field"> | |
<mat-label>Type</mat-label> | |
<mat-select formControlName="type"> | |
@for (type of variableTypes; track type) { | |
<mat-option [value]="type">{{ type }}</mat-option> | |
} | |
</mat-select> | |
</mat-form-field> | |
<mat-form-field appearance="outline" class="path-field"> | |
<mat-label>JSON Path</mat-label> | |
<input matInput formControlName="json_path" placeholder="$.data.bookingReference"> | |
<mat-hint>JSONPath expression to extract value</mat-hint> | |
</mat-form-field> | |
</div> | |
<button mat-button color="warn" (click)="removeResponseMapping(i)"> | |
<mat-icon>delete</mat-icon> | |
Remove Mapping | |
</button> | |
</div> | |
</mat-expansion-panel> | |
} | |
</div> | |
<!-- Retry Settings --> | |
<mat-divider></mat-divider> | |
<div class="retry-section" [formGroup]="$any(form.get('retry'))"> | |
<h3>Retry Settings</h3> | |
<div class="row"> | |
<mat-form-field appearance="outline"> | |
<mat-label>Retry Count</mat-label> | |
<input matInput type="number" formControlName="retry_count"> | |
<mat-hint>Number of retry attempts</mat-hint> | |
</mat-form-field> | |
<mat-form-field appearance="outline"> | |
<mat-label>Backoff (seconds)</mat-label> | |
<input matInput type="number" formControlName="backoff_seconds"> | |
<mat-hint>Delay between retries</mat-hint> | |
</mat-form-field> | |
<mat-form-field appearance="outline"> | |
<mat-label>Strategy</mat-label> | |
<mat-select formControlName="strategy"> | |
@for (strategy of retryStrategies; track strategy) { | |
<mat-option [value]="strategy">{{ strategy }}</mat-option> | |
} | |
</mat-select> | |
</mat-form-field> | |
</div> | |
</div> | |
</div> | |
</mat-tab> | |
<!-- Test Tab --> | |
<mat-tab label="Test"> | |
<div class="tab-content"> | |
<div class="test-section"> | |
<h3>Test API Call</h3> | |
<div class="test-controls"> | |
<button mat-raised-button color="primary" | |
(click)="testAPI()" | |
[disabled]="testing || !form.get('url')?.valid || !form.get('method')?.valid"> | |
@if (testing) { | |
<ng-container> | |
<mat-icon class="spin">sync</mat-icon> | |
Testing... | |
</ng-container> | |
} @else { | |
<ng-container> | |
<mat-icon>play_arrow</mat-icon> | |
Test API | |
</ng-container> | |
} | |
</button> | |
<button mat-button (click)="updateTestRequestJson()"> | |
<mat-icon>refresh</mat-icon> | |
Generate Test Data | |
</button> | |
</div> | |
<app-json-editor | |
[(ngModel)]="testRequestJson" | |
label="Test Request Body" | |
placeholder="Enter test request JSON here" | |
hint="Variables will be replaced with test values" | |
[rows]="10"> | |
</app-json-editor> | |
@if (testResult) { | |
<mat-divider></mat-divider> | |
<div class="test-result" [class.success]="testResult.success" [class.error]="!testResult.success"> | |
<h4>Test Result</h4> | |
@if (testResult.success) { | |
<div class="result-status"> | |
<mat-icon>check_circle</mat-icon> | |
<span>Success ({{ testResult.status_code }})</span> | |
</div> | |
} @else { | |
<div class="result-status"> | |
<mat-icon>error</mat-icon> | |
<span>Failed: {{ testResult.error }}</span> | |
</div> | |
} | |
@if (testResult.response_time) { | |
<p><strong>Response Time:</strong> {{ testResult.response_time }}ms</p> | |
} | |
@if (testResult.response_headers) { | |
<mat-expansion-panel> | |
<mat-expansion-panel-header> | |
<mat-panel-title>Response Headers</mat-panel-title> | |
</mat-expansion-panel-header> | |
<mat-form-field appearance="outline" class="full-width"> | |
<mat-label>Headers</mat-label> | |
<textarea matInput | |
[value]="testResult.response_headers | json" | |
rows="6" | |
readonly></textarea> | |
</mat-form-field> | |
</mat-expansion-panel> | |
} | |
@if (testResult.response_body) { | |
<mat-expansion-panel [expanded]="true"> | |
<mat-expansion-panel-header> | |
<mat-panel-title>Response Body</mat-panel-title> | |
</mat-expansion-panel-header> | |
<mat-form-field appearance="outline" class="full-width"> | |
<mat-label>Response</mat-label> | |
<textarea matInput | |
[value]="testResult.response_body | json" | |
rows="12" | |
readonly></textarea> | |
</mat-form-field> | |
</mat-expansion-panel> | |
} | |
@if (testResult.request_body) { | |
<mat-expansion-panel> | |
<mat-expansion-panel-header> | |
<mat-panel-title>Request Details</mat-panel-title> | |
</mat-expansion-panel-header> | |
<mat-form-field appearance="outline" class="full-width"> | |
<mat-label>Actual Request Sent</mat-label> | |
<textarea matInput | |
[value]="testResult.request_body | json" | |
rows="8" | |
readonly></textarea> | |
</mat-form-field> | |
@if (testResult.request_headers) { | |
<mat-form-field appearance="outline" class="full-width"> | |
<mat-label>Request Headers</mat-label> | |
<textarea matInput | |
[value]="testResult.request_headers | json" | |
rows="6" | |
readonly></textarea> | |
</mat-form-field> | |
} | |
</mat-expansion-panel> | |
} | |
@if (testResult.extracted_values && testResult.extracted_values.length > 0) { | |
<mat-expansion-panel> | |
<mat-expansion-panel-header> | |
<mat-panel-title>Extracted Values</mat-panel-title> | |
</mat-expansion-panel-header> | |
<table mat-table [dataSource]="testResult.extracted_values" class="full-width"> | |
<ng-container matColumnDef="variable"> | |
<th mat-header-cell *matHeaderCellDef>Variable</th> | |
<td mat-cell *matCellDef="let element">{{ element.variable_name }}</td> | |
</ng-container> | |
<ng-container matColumnDef="value"> | |
<th mat-header-cell *matHeaderCellDef>Value</th> | |
<td mat-cell *matCellDef="let element">{{ element.value }}</td> | |
</ng-container> | |
<ng-container matColumnDef="type"> | |
<th mat-header-cell *matHeaderCellDef>Type</th> | |
<td mat-cell *matCellDef="let element">{{ element.type }}</td> | |
</ng-container> | |
<tr mat-header-row *matHeaderRowDef="['variable', 'value', 'type']"></tr> | |
<tr mat-row *matRowDef="let row; columns: ['variable', 'value', 'type'];"></tr> | |
</table> | |
</mat-expansion-panel> | |
} | |
</div> | |
} | |
</div> | |
</div> | |
</mat-tab> | |
</mat-tab-group> | |
</mat-dialog-content> | |
<mat-dialog-actions align="end"> | |
<button mat-button (click)="cancel()"> | |
@if (data.mode === 'test') { | |
Close | |
} @else { | |
Cancel | |
} | |
</button> | |
@if (data.mode !== 'test') { | |
<button mat-raised-button color="primary" | |
(click)="save()" | |
[disabled]="saving || form.invalid"> | |
@if (saving) { | |
<ng-container> | |
<mat-icon class="spin">sync</mat-icon> | |
Saving... | |
</ng-container> | |
} @else { | |
@if (data.mode === 'create' || data.mode === 'duplicate') { | |
Create | |
} @else { | |
Update | |
} | |
} | |
</button> | |
} | |
</mat-dialog-actions> |