Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[READY] Add configuration options for some apps in the manager #253

Merged
merged 5 commits into from
Mar 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions static/skywire-manager-src/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ import { SelectColumnComponent } from './components/layout/select-column/select-
import { SelectOptionComponent } from './components/layout/select-option/select-option.component';
import { SelectPageComponent } from './components/layout/paginator/select-page/select-page.component';
import { TerminalComponent } from './components/pages/node/actions/terminal/terminal.component';
import { SkysocksSettingsComponent } from './components/pages/node/apps/node-apps/skysocks-settings/skysocks-settings.component';
import {
SkysocksClientSettingsComponent
} from './components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component';

const globalRippleConfig: RippleGlobalOptions = {
disabled: true,
Expand Down Expand Up @@ -179,6 +183,8 @@ const globalRippleConfig: RippleGlobalOptions = {
SelectOptionComponent,
SelectPageComponent,
TerminalComponent,
SkysocksSettingsComponent,
SkysocksClientSettingsComponent,
],
entryComponents: [
ConfigurationComponent,
Expand Down Expand Up @@ -207,6 +213,8 @@ const globalRippleConfig: RippleGlobalOptions = {
SelectOptionComponent,
SelectPageComponent,
TerminalComponent,
SkysocksSettingsComponent,
SkysocksClientSettingsComponent,
],
imports: [
BrowserModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@
</button>
</td>
<td class="actions">
<button
(click)="config(app)"
mat-icon-button
[matTooltip]="'apps.settings' | translate"
class="big-action-button hard-grey-button-background"
*ngIf="appsWithConfig.has(app.name)"
>
<mat-icon [inline]="true">settings</mat-icon>
</button>
<button
(click)="viewLogs(app)"
mat-icon-button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

.actions {
text-align: right;
width: 90px;
width: 120px;
}

@media (max-width: (map-get($grid-breakpoints, md) - 1)) , (min-width: map-get($grid-breakpoints, lg)) and (max-width: (map-get($grid-breakpoints, xl) - 1)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import { ConfirmationComponent } from '../../../../../layout/confirmation/confir
import { SnackbarService } from '../../../../../../services/snackbar.service';
import { SelectableOption, SelectOptionComponent } from 'src/app/components/layout/select-option/select-option.component';
import { SelectColumnComponent, SelectedColumn } from 'src/app/components/layout/select-column/select-column.component';
import { SkysocksSettingsComponent } from '../skysocks-settings/skysocks-settings.component';
import { processServiceError } from 'src/app/utils/errors';
import { OperationError } from 'src/app/utils/operation-error';
import { SkysocksClientSettingsComponent } from '../skysocks-client-settings/skysocks-client-settings.component';

/**
* List of the columns that can be used to sort the data.
Expand Down Expand Up @@ -68,6 +70,12 @@ export class NodeAppsListComponent implements OnDestroy {
this.recalculateElementsToShow();
}

// List with the names of all the apps which can be configured directly on the manager.
appsWithConfig = new Map<string, boolean>([
['skysocks', true],
['skysocks-client', true],
]);

allApps: Application[];
appsToShow: Application[];
appsMap: Map<string, Application>;
Expand Down Expand Up @@ -219,13 +227,22 @@ export class NodeAppsListComponent implements OnDestroy {
}
];

if (this.appsWithConfig.has(app.name)) {
options.push({
icon: 'settings',
label: 'apps.settings',
});
}

SelectOptionComponent.openDialog(this.dialog, options).afterClosed().subscribe((selectedOption: number) => {
if (selectedOption === 1) {
this.viewLogs(app);
} else if (selectedOption === 2) {
this.changeAppState(app);
} else if (selectedOption === 3) {
this.changeAppAutostart(app);
} else if (selectedOption === 4) {
this.config(app);
}
});
}
Expand Down Expand Up @@ -313,6 +330,19 @@ export class NodeAppsListComponent implements OnDestroy {
LogComponent.openDialog(this.dialog, app);
}

/**
* Shows the appropriate modal window for configuring the app.
*/
config(app: Application): void {
if (app.name === 'skysocks') {
SkysocksSettingsComponent.openDialog(this.dialog, app.name);
} else if (app.name === 'skysocks-client') {
SkysocksClientSettingsComponent.openDialog(this.dialog, app.name);
} else {
this.snackbarService.showError('apps.error');
}
}

/**
* Changes the column and/or order used for sorting the data.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<app-dialog [headline]="'apps.skysocks-client-settings.title' | translate">
<mat-tab-group>
<!-- Form. -->
<mat-tab [label]="'apps.skysocks-client-settings.remote-visor-tab' | translate">
<form [formGroup]="form">
<mat-form-field>
<input
id="pk"
formControlName="pk"
maxlength="66"
[placeholder]="'apps.skysocks-client-settings.public-key' | translate"
#firstInput
matInput
>
<mat-error>
<ng-container *ngIf="!this.form.get('pk').hasError('pattern');else hexError">
{{ 'apps.skysocks-client-settings.remote-key-length-error' | translate }}
</ng-container>
</mat-error>
<ng-template #hexError>
{{ 'apps.skysocks-client-settings.remote-key-chars-error' | translate }}
</ng-template>
</mat-form-field>

<app-button
#button
(action)="saveChanges()"
type="mat-raised-button"
[disabled]="!form.valid"
color="primary"
class="float-right"
>
{{ 'apps.skysocks-client-settings.save' | translate }}
</app-button>
</form>
</mat-tab>
<!-- History. -->
<mat-tab [label]="'apps.skysocks-client-settings.history-tab' | translate">
<!-- Msg shown if there is no history. -->
<div *ngIf="history.length === 0">
<div class="no-history-text">
<mat-icon [inline]="true">error</mat-icon>
{{ 'apps.skysocks-client-settings.no-history' | translate:{number: maxHistoryElements} }}
</div>
</div>

<div *ngIf="history.length > 0" class="top-history-margin"></div>
<ng-container *ngFor="let entry of history">
<button
mat-button
class="grey-button-background w-100"
(click)="saveChanges(entry)"
>
<div class="history-button-content">
<span>{{ entry }}</span>
</div>
</button>
</ng-container>
</mat-tab>
</mat-tab-group>
</app-dialog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@import "variables";

form {
margin-top: 15px;
}

.no-history-text {
margin-top: 20px;
margin-bottom: 2px;
text-align: center;
color: $black;

mat-icon {
position: relative;
top: 2px;
}
}

.top-history-margin {
width: 100%;
height: 15px;
}

.history-button-content {
text-align: left;
padding: 5px 0px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { SkysocksClientSettingsComponent } from './skysocks-client-settings.component';

describe('SkysocksClientSettingsComponent', () => {
let component: SkysocksClientSettingsComponent;
let fixture: ComponentFixture<SkysocksClientSettingsComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SkysocksClientSettingsComponent ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(SkysocksClientSettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { Component, OnInit, ViewChild, OnDestroy, ElementRef, Inject } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialogRef, MatDialog, MatDialogConfig, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Subscription } from 'rxjs';

import { ButtonComponent } from '../../../../../layout/button/button.component';
import { NodeComponent } from '../../../node.component';
import { SnackbarService } from '../../../../../../services/snackbar.service';
import { AppConfig } from 'src/app/app.config';
import { processServiceError } from 'src/app/utils/errors';
import { OperationError } from 'src/app/utils/operation-error';
import { AppsService } from 'src/app/services/apps.service';
import GeneralUtils from 'src/app/utils/generalUtils';

/**
* Modal window used for configuring the Skysocks-client app.
*/
@Component({
selector: 'app-skysocks-client-settings',
templateUrl: './skysocks-client-settings.component.html',
styleUrls: ['./skysocks-client-settings.component.scss']
})
export class SkysocksClientSettingsComponent implements OnInit, OnDestroy {
// Key for saving the history in persistent storage.
private readonly historyStorageKey = 'SkysocksClientHistory';
// Max elements the history can contain.
readonly maxHistoryElements = 10;

@ViewChild('button', { static: false }) button: ButtonComponent;
@ViewChild('firstInput', { static: false }) firstInput: ElementRef;
form: FormGroup;
// Entries to show on the history.
history: string[];

// If the operation in being currently made.
private working = false;
// Last public key set to be sent to the backend.
private lastPublicKey: string;
private operationSubscription: Subscription;

/**
* Opens the modal window. Please use this function instead of opening the window "by hand".
*/
public static openDialog(dialog: MatDialog, appName: string): MatDialogRef<SkysocksClientSettingsComponent, any> {
const config = new MatDialogConfig();
config.data = appName;
config.autoFocus = false;
config.width = AppConfig.mediumModalWidth;

return dialog.open(SkysocksClientSettingsComponent, config);
}

constructor(
@Inject(MAT_DIALOG_DATA) private data: string,
private dialogRef: MatDialogRef<SkysocksClientSettingsComponent>,
private appsService: AppsService,
private formBuilder: FormBuilder,
private snackbarService: SnackbarService,
private dialog: MatDialog,
) { }

ngOnInit() {
// Get the history.
const retrievedHistory = localStorage.getItem(this.historyStorageKey);
if (retrievedHistory) {
this.history = JSON.parse(retrievedHistory);
} else {
this.history = [];
}

this.form = this.formBuilder.group({
'pk': ['', Validators.compose([
Validators.required,
Validators.minLength(66),
Validators.maxLength(66),
Validators.pattern('^[0-9a-fA-F]+$')])
],
});

setTimeout(() => (this.firstInput.nativeElement as HTMLElement).focus());
}

ngOnDestroy() {
if (this.operationSubscription) {
this.operationSubscription.unsubscribe();
}
}

/**
* Saves the settings.
*/
saveChanges(publicKey: string = null) {
if ((!this.form.valid && !publicKey) || this.working) {
return;
}

this.lastPublicKey = publicKey ? publicKey : this.form.get('pk').value;

// Ask for confirmation.
const confirmationMsg = 'apps.skysocks-client-settings.change-key-confirmation';
const confirmationDialog = GeneralUtils.createConfirmationDialog(this.dialog, confirmationMsg);
confirmationDialog.componentInstance.operationAccepted.subscribe(() => {
confirmationDialog.close();
this.continueSavingChanges();
});
}

private continueSavingChanges() {
this.button.showLoading();
this.working = true;

this.operationSubscription = this.appsService.changeAppSettings(
// The node pk is obtained from the currently openned node page.
NodeComponent.getCurrentNodeKey(),
this.data,
{ pk: this.lastPublicKey },
).subscribe({
next: this.onSuccess.bind(this),
error: this.onError.bind(this)
});
}

private onSuccess() {
// Remove any repeated entry from the history.
this.history = this.history.filter(value => value !== this.lastPublicKey);

// Save the new public key on the history.
this.history = [this.lastPublicKey].concat(this.history);
if (this.history.length > this.maxHistoryElements) {
const itemsToRemove = this.history.length - this.maxHistoryElements;
this.history.splice(this.history.length - itemsToRemove, itemsToRemove);
}

const dataToSave = JSON.stringify(this.history);
localStorage.setItem(this.historyStorageKey, dataToSave);

// Close the window.
NodeComponent.refreshCurrentDisplayedData();
this.snackbarService.showDone('apps.skysocks-client-settings.changes-made');
this.dialogRef.close();
}

private onError(err: OperationError) {
this.working = false;
this.button.showError();
err = processServiceError(err);

this.snackbarService.showError(err);
}
}
Loading