-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(frontend): add UI dialog to configure http headers that will be …
…added by the microserivce to requests going to the http servers
- Loading branch information
Showing
6 changed files
with
310 additions
and
78 deletions.
There are no files selected for viewing
119 changes: 119 additions & 0 deletions
119
...c/app/cloud-http-proxy/cloud-http-proxy-settings/cloud-http-proxy-settings.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
<div class="viewport-modal"> | ||
<div class="modal-header dialog-header"> | ||
<i c8yIcon="wrench"></i> | ||
<h4 id="modal-title">{{ "Cloud HTTP proxy configuration" | translate }}</h4> | ||
</div> | ||
<div class="modal-inner-scroll"> | ||
<div id="modal-body" class="bg-component"> | ||
<c8y-loading *ngIf="loading; else content"></c8y-loading> | ||
<ng-template #content> | ||
<div class="p-4"> | ||
<p> | ||
This section allows you to define HTTP headers that should be set by | ||
the proxy microservice. You could e.g. set an | ||
<b>authorization</b> header containing credentials for the http | ||
server you are trying to access. In some cases you might need to | ||
also specify a <b>host</b> header, in case the http server depends | ||
on the actual host that was used to access it. | ||
</p> | ||
<div *ngIf="entries | keyvalue as currentHeaders"> | ||
<fieldset class="c8y-fieldset" *ngIf="currentHeaders?.length"> | ||
<legend translate>Current headers</legend> | ||
<div | ||
*ngFor="let item of currentHeaders" | ||
class="d-flex j-c-center" | ||
> | ||
<c8y-form-group> | ||
<label for="headerValue">{{ item.value.cleanedKey }}</label> | ||
<div class="input-group"> | ||
<input | ||
class="form-control" | ||
id="headerValue" | ||
[ngModel]="item.value.value" | ||
type="text" | ||
placeholder="HTTP header e.g. Basic [...]" | ||
disabled="" | ||
/> | ||
<div class="input-group-btn"> | ||
<button | ||
class="btn-dot btn-dot--danger" | ||
(click)="removeSetting(item.key)" | ||
[attr.aria-label]="'Remove'" | ||
> | ||
<i c8yIcon="minus-circle"></i> | ||
</button> | ||
</div> | ||
</div> | ||
</c8y-form-group> | ||
</div> | ||
</fieldset> | ||
</div> | ||
|
||
<div> | ||
<fieldset class="c8y-fieldset"> | ||
<legend translate>New header</legend> | ||
<div class="d-flex a-i-center j-c-around"> | ||
<c8y-form-group> | ||
<label for="headerKey" translate>HTTP header</label> | ||
<input | ||
class="form-control" | ||
id="headerKey" | ||
[(ngModel)]="newValue.key" | ||
type="text" | ||
placeholder="HTTP header e.g. Authorization" | ||
/> | ||
</c8y-form-group> | ||
|
||
<c8y-form-group> | ||
<label for="headerValue" translate>HTTP header value</label> | ||
<input | ||
class="form-control" | ||
id="headerValue" | ||
[(ngModel)]="newValue.value" | ||
type="text" | ||
placeholder="HTTP header e.g. Basic [...]" | ||
/> | ||
</c8y-form-group> | ||
|
||
<c8y-form-group> | ||
<label for="encryptTenantOption" class="c8y-checkbox"> | ||
<input | ||
class="form-control" | ||
id="encryptTenantOption" | ||
[(ngModel)]="newValue.encrypt" | ||
type="checkbox" | ||
/> | ||
<span></span> | ||
<span translate>Encrypt</span> | ||
</label> | ||
</c8y-form-group> | ||
</div> | ||
<div class="d-flex a-i-center j-c-around"> | ||
<button | ||
type="button" | ||
class="btn btn-default m-b-8" | ||
(click)="addHeader()" | ||
[disabled]="!newValue.value || !newValue.key" | ||
> | ||
<i c8yIcon="plus"></i> | ||
Add Header | ||
</button> | ||
</div> | ||
</fieldset> | ||
</div> | ||
</div> | ||
</ng-template> | ||
</div> | ||
</div> | ||
|
||
<div class="modal-footer"> | ||
<button | ||
class="btn btn-primary" | ||
type="button" | ||
title="{{ 'Close' | translate }}" | ||
(click)="close()" | ||
> | ||
{{ "Close" | translate }} | ||
</button> | ||
</div> | ||
</div> |
136 changes: 136 additions & 0 deletions
136
...src/app/cloud-http-proxy/cloud-http-proxy-settings/cloud-http-proxy-settings.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { Component, OnInit } from '@angular/core'; | ||
import { FetchClient, TenantOptionsService } from '@c8y/client'; | ||
import { proxyContextPath } from '../cloud-http-proxy.model'; | ||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; | ||
import { AlertService, CoreModule } from '@c8y/ngx-components'; | ||
import { KeyValuePipe, NgFor } from '@angular/common'; | ||
import { BsModalRef } from 'ngx-bootstrap/modal'; | ||
import { ProxyTrackingService } from '../proxy-tracking.service'; | ||
|
||
@Component({ | ||
selector: 'cloud-http-proxy-settings', | ||
templateUrl: './cloud-http-proxy-settings.component.html', | ||
standalone: true, | ||
imports: [NgFor, ReactiveFormsModule, FormsModule, KeyValuePipe, CoreModule], | ||
}) | ||
export class CloudHttpProxySettingsComponent implements OnInit { | ||
loading = true; | ||
cloudProxyConfigId: string | undefined; | ||
cloudProxyDeviceId: string | undefined; | ||
secure?: boolean | undefined; | ||
|
||
newValue = this.resetNewValue(); | ||
|
||
entries = new Map<string, { value: string; cleanedKey: string }>(); | ||
|
||
constructor( | ||
private modalRef: BsModalRef, | ||
private tracking: ProxyTrackingService, | ||
private alert: AlertService, | ||
private tenantOptions: TenantOptionsService, | ||
private fetch: FetchClient | ||
) {} | ||
|
||
async ngOnInit() { | ||
try { | ||
const options = await this.listTenantOptions(); | ||
const keySuffix = this.keySuffix(); | ||
for (const [key, value] of Object.entries(options)) { | ||
if (!key.endsWith(keySuffix)) { | ||
continue; | ||
} | ||
this.addKeyValuePair(key, value); | ||
} | ||
} catch (e) { | ||
this.alert.danger('Failed to load tenant options.'); | ||
} | ||
|
||
this.loading = false; | ||
} | ||
|
||
addHeader() { | ||
this.addNewEntry( | ||
this.newValue.key, | ||
this.newValue.value, | ||
this.newValue.encrypt | ||
); | ||
} | ||
|
||
async addNewEntry(headerKey: string, headerValue: string, encrypt = true) { | ||
const keySuffix = this.keySuffix(); | ||
const transformedKey = headerKey.toLowerCase(); | ||
const key = `${ | ||
encrypt ? 'credentials.' : '' | ||
}rca-http-header-${transformedKey}${keySuffix}`; | ||
this.tracking.triggerGainSightEvent('save-header-in-tenant-option', { | ||
tenantOptionKey: transformedKey, | ||
encrypt, | ||
cloudProxyDeviceId: this.cloudProxyDeviceId, | ||
cloudProxyConfigId: this.cloudProxyConfigId, | ||
}); | ||
try { | ||
const result = await this.tenantOptions.create({ | ||
category: proxyContextPath, | ||
key, | ||
value: headerValue, | ||
}); | ||
this.addKeyValuePair(key, encrypt ? '<<Encrypted>>' : headerValue); | ||
this.alert.success('Tenant option saved.'); | ||
this.newValue = this.resetNewValue(); | ||
} catch (e) { | ||
this.alert.addServerFailure(e); | ||
} | ||
} | ||
|
||
addKeyValuePair(key: string, value: string) { | ||
this.entries.set(key, { cleanedKey: this.cleanKey(key), value }); | ||
} | ||
|
||
async listTenantOptions(): Promise<{ [key: string]: string }> { | ||
const result = await this.fetch.fetch( | ||
`/tenant/options/${proxyContextPath}` | ||
); | ||
if (result.status === 404) { | ||
return {}; | ||
} | ||
if (result.status !== 200) { | ||
throw Error('Wrong status code', { cause: result }); | ||
} | ||
const body = await result.json(); | ||
|
||
return body; | ||
} | ||
|
||
close() { | ||
this.modalRef.hide(); | ||
} | ||
|
||
async removeSetting(key: string) { | ||
try { | ||
await this.tenantOptions.delete({ category: proxyContextPath, key: key }); | ||
this.entries.delete(key); | ||
this.alert.success('Header removed.'); | ||
} catch (e) { | ||
this.alert.addServerFailure(e); | ||
} | ||
} | ||
|
||
private cleanKey(key: string) { | ||
const removedPrefix = key.replace(/.*rca-http-header-/, ''); | ||
const removedSuffix = removedPrefix.replace(this.keySuffix(), ''); | ||
|
||
return removedSuffix; | ||
} | ||
|
||
private resetNewValue() { | ||
return { | ||
key: '', | ||
value: '', | ||
encrypt: false, | ||
}; | ||
} | ||
|
||
private keySuffix() { | ||
return `-${this.cloudProxyDeviceId}-${this.cloudProxyConfigId}`; | ||
} | ||
} |
10 changes: 4 additions & 6 deletions
10
frontend/src/app/cloud-http-proxy/cloud-http-proxy.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.