Skip to content

Commit

Permalink
feat(frontend): add support for sub paths (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
reey authored Apr 15, 2024
1 parent 76badb6 commit b0c7090
Show file tree
Hide file tree
Showing 7 changed files with 346 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<div class="viewport-modal">
<div class="modal-header dialog-header">
<i c8yIcon="wrench"></i>
<h4 id="modal-title">{{ "Cloud HTTP proxy paths" | translate }}</h4>
</div>
<div class="modal-inner-scroll">
<div id="modal-body" class="bg-component">
<div class="p-4">
<p>
This section allows you to define specific pathes per configuration
that you can directly navigate to. Instead of navigating to the root
path ("/") on the web server, you can provide a specific path here or
even multiple ones.
</p>
<div>
<fieldset class="c8y-fieldset" *ngIf="configs.length">
<legend translate>Configured paths</legend>
<div *ngFor="let item of configs" class="d-flex j-c-center">
<c8y-form-group>
<label for="headerValue">{{ item.label }}</label>
<div class="input-group">
<input
class="form-control"
id="headerValue"
[ngModel]="item.path"
type="text"
disabled=""
/>
<div class="input-group-btn">
<button
class="btn-dot btn-dot--danger"
(click)="removePath(item.path)"
[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 path</legend>
<div class="d-flex a-i-center j-c-around">
<c8y-form-group>
<label for="headerKey" translate>Label</label>
<input
class="form-control"
id="headerKey"
[(ngModel)]="newValue.label"
type="text"
placeholder="e.g. Dashboard"
/>
</c8y-form-group>

<c8y-form-group>
<label for="headerValue" translate>Path</label>
<input
class="form-control"
id="headerValue"
[(ngModel)]="newValue.path"
type="text"
placeholder="e.g. /dashboard/"
/>
</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)="addPath()"
[disabled]="!newValue.label || !newValue.path"
>
<i c8yIcon="plus"></i>
Add Path
</button>
</div>
</fieldset>
</div>
</div>
</div>
</div>

<div class="modal-footer">
<button
class="btn btn-primary"
type="button"
title="{{ 'Save' | translate }}"
(click)="save()"
>
{{ "Save" | translate }}
</button>
<button
class="btn btn-default"
type="button"
title="{{ 'Cancel' | translate }}"
(click)="cancel()"
>
{{ "Cancel" | translate }}
</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { CloudHTTPProxyPathConfig, CloudHTTPProxyPathConfigs, RemoteAccessService } from './remote-access.service';
import { KeyValuePipe, NgFor } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CoreModule, NavigatorService, TabsService } from '@c8y/ngx-components';
import { IManagedObject } from '@c8y/client';
import { BsModalRef } from 'ngx-bootstrap/modal';

@Component({
selector: 'cloud-http-proxy-path',
templateUrl: './cloud-http-proxy-path.component.html',
standalone: true,
imports: [NgFor, ReactiveFormsModule, FormsModule, KeyValuePipe, CoreModule]
})
export class CloudHttpProxyPathComponent implements OnInit {
device: IManagedObject | undefined;
configs: CloudHTTPProxyPathConfig[] = [];
allConfigs: CloudHTTPProxyPathConfigs | undefined;
cloudProxyConfigId: string | undefined;

newValue: CloudHTTPProxyPathConfig = this.resetNewValue();

constructor(private remoteAccess: RemoteAccessService, private modalRef: BsModalRef, private tabs: TabsService) {
}

ngOnInit() {
console.log(this.device);
this.allConfigs = this.device?.[RemoteAccessService.pathFragment] || {};
if (!this.cloudProxyConfigId || !this.allConfigs) {
this.configs = [];
return;
}
this.configs = this.allConfigs[this.cloudProxyConfigId] || [];
}

removePath(path: string) {
this.configs = this.configs.filter(config => config.path !== path);
}

addPath() {
this.configs.push(this.newValue);
this.newValue = this.resetNewValue();
}

cancel() {
this.modalRef.hide();
}

async save() {
if (!this.cloudProxyConfigId || !this.device || !this.allConfigs) {
return;
}
this.allConfigs[this.cloudProxyConfigId] = this.configs;
await this.remoteAccess.updatePathConfig(this.device.id, this.allConfigs);
this.device[RemoteAccessService.pathFragment] = this.allConfigs;
this.tabs.refresh();
this.modalRef.hide();
}

private resetNewValue() {
return {
label: '',
path: ''
};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { FetchClient, InventoryService } from '@c8y/client';

export interface CloudHTTPProxyPathConfig {
path: string;
label: string;
}

export interface CloudHTTPProxyPathConfigs {
[key: string]: CloudHTTPProxyPathConfig[];
}

@Injectable({
providedIn: 'root'
})
export class RemoteAccessService {
static pathFragment = 'cloudHTTPProxyPathConfigs';

constructor(private inventory: InventoryService) {}

async updatePathConfig(deviceId: string, config: CloudHTTPProxyPathConfigs) {
const response = await this.inventory.update({
id: deviceId,
[RemoteAccessService.pathFragment]: config
});

return response.data[RemoteAccessService.pathFragment];
}

}
60 changes: 50 additions & 10 deletions frontend/src/app/cloud-http-proxy/cloud-http-proxy-tab.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import { ExtensionFactory, Tab, ViewContext } from '@c8y/ngx-components';
import { Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { CloudHttpProxyAvailabilityService } from './cloud-http-proxy-available';
import {
CloudHTTPProxyPathConfig,
CloudHTTPProxyPathConfigs,
RemoteAccessService,
} from './cloud-http-proxy-path/remote-access.service';

@Injectable({
providedIn: 'root',
Expand Down Expand Up @@ -86,15 +91,50 @@ export class CloudHttpProxyTabFactory implements ExtensionFactory<Tab> {
return httpPrefixedPassthroughConfigs
.filter((tmp) => tmp.name.startsWith(prefix))
.map(({ id, name }) => {
const newName = name.replace(prefix, '');
const tab: Tab = {
path: `/device/${device.id}/${
secure ? 'secure-' : ''
}cloud-http-proxy/${id}`,
label: `${newName}`,
icon: `window-restore`,
};
return tab;
});
const tabs = this.getCustomPathTabs(id, device, secure);
if (tabs.length) {
return tabs;
}
return [this.getDefaultTab(name, prefix, device, secure, id)];
}).flat();
}

private getCustomPathTabs(
configId: string,
device: IManagedObject,
secure?: boolean
) {
const customPathConfigs: CloudHTTPProxyPathConfigs =
device[RemoteAccessService.pathFragment] || {};
const customPathConfigsForId: CloudHTTPProxyPathConfig[] =
customPathConfigs[configId] || [];
return customPathConfigsForId.map((config, index) => {
const tab: Tab = {
path: `/device/${device.id}/${
secure ? 'secure-' : ''
}cloud-http-proxy/${configId}/${index}`,
label: config.label,
icon: `window-restore`,
};
return tab;
});
}

private getDefaultTab(
name: string,
prefix: string,
device: IManagedObject,
secure: boolean | undefined,
id: string
) {
const newName = name.replace(prefix, '');
const tab: Tab = {
path: `/device/${device.id}/${
secure ? 'secure-' : ''
}cloud-http-proxy/${id}`,
label: `${newName}`,
icon: `window-restore`,
};
return tab;
}
}
40 changes: 35 additions & 5 deletions frontend/src/app/cloud-http-proxy/cloud-http-proxy.component.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,38 @@
<c8y-action-bar-item *c8yIfAllowed="['ROLE_OPTION_MANAGEMENT_ADMIN']" placement="left">
<button class="btn btn-link" (click)="openSettingsModal()">
<i c8yIcon="wrench"></i>
Configure
</button>
<c8y-action-bar-item [itemClass]="'navbar-form'">
<div
class="dropdown"
dropdown
#basicDropdown="bs-dropdown"
>
<button
type="button"
title="dropdown-toggle"
dropdownToggle
class="btn btn-default dropdown-toggle c8y-dropdown"
>
<i c8yIcon="wrench"></i>
<span>&nbsp;</span>
<span>Configure</span>
<span class="caret"></span>
</button>
<ul
class="dropdown-menu"
*dropdownMenu
>
<li>
<button type="button" (click)="openSettingsModal()">
<i c8yIcon="wrench"></i>
{{'Headers' | translate}}
</button>
</li>
<li>
<button type="button" (click)="openPathsModal()">
<i c8yIcon="wrench"></i>
{{'Paths' | translate}}
</button>
</li>
</ul>
</div>
</c8y-action-bar-item>

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

0 comments on commit b0c7090

Please sign in to comment.