Skip to content

Commit

Permalink
feat(frontend): add UI dialog to configure http headers that will be …
Browse files Browse the repository at this point in the history
…added by the microserivce to requests going to the http servers
  • Loading branch information
reey committed Apr 10, 2024
1 parent a445b64 commit f2ad509
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 78 deletions.
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>
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}`;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
<c8y-action-bar-item *ngIf="hasTenantOptionAdminPermission" placement="left">
<div class="row">
<input type="text" [(ngModel)]="value" />
<button class="btn btn-link" (click)="saveAuthInTenantOption()">
Save
<c8y-action-bar-item *c8yIfAllowed="['ROLE_OPTION_MANAGEMENT_ADMIN']" placement="left">
<button class="btn btn-link" (click)="openSettingsModal()">
<i c8yIcon="wrench"></i>
Configure
</button>
</div>
</c8y-action-bar-item>

<c8y-action-bar-item placement="right"
Expand Down
Loading

0 comments on commit f2ad509

Please sign in to comment.