From 1628eff4dd7ebc4b4f08e2639e82e9cefe1a37ad Mon Sep 17 00:00:00 2001 From: Senyoret1 <34079003+Senyoret1@users.noreply.github.com> Date: Thu, 16 Apr 2020 16:21:47 -0400 Subject: [PATCH 1/6] Additional features for configuring skysocks-client in the manager --- .../src/app/app.datatypes.ts | 10 +- .../skywire-manager-src/src/app/app.module.ts | 10 + .../edit-skysocks-client-note.component.html | 8 + .../edit-skysocks-client-note.component.scss | 0 ...dit-skysocks-client-note.component.spec.ts | 25 ++ .../edit-skysocks-client-note.component.ts | 54 ++++ .../skysocks-client-filter.component.html | 42 +++ .../skysocks-client-filter.component.scss | 0 .../skysocks-client-filter.component.spec.ts | 25 ++ .../skysocks-client-filter.component.ts | 118 +++++++++ .../skysocks-client-settings.component.html | 145 ++++++++++- .../skysocks-client-settings.component.scss | 80 +++++- .../skysocks-client-settings.component.ts | 242 ++++++++++++++++-- .../app/services/proxy-discovery.service.ts | 62 +++++ .../src/assets/i18n/en.json | 37 ++- .../src/assets/i18n/es.json | 37 ++- .../src/assets/i18n/es_base.json | 37 ++- 17 files changed, 890 insertions(+), 42 deletions(-) create mode 100644 static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.html create mode 100644 static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.scss create mode 100644 static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.spec.ts create mode 100644 static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.ts create mode 100644 static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.html create mode 100644 static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.scss create mode 100644 static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.spec.ts create mode 100644 static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.ts create mode 100644 static/skywire-manager-src/src/app/services/proxy-discovery.service.ts diff --git a/static/skywire-manager-src/src/app/app.datatypes.ts b/static/skywire-manager-src/src/app/app.datatypes.ts index c9d2ee0f90..a3734ad498 100644 --- a/static/skywire-manager-src/src/app/app.datatypes.ts +++ b/static/skywire-manager-src/src/app/app.datatypes.ts @@ -54,7 +54,15 @@ export interface HealthInfo { setup_node?: number; } - +export class ProxyDiscoveryEntry { + publicKeyPort: string; + country?: string; + city?: string; + location?: string; + status?: string; + available?: boolean; + updatedAt?: string; +} // old diff --git a/static/skywire-manager-src/src/app/app.module.ts b/static/skywire-manager-src/src/app/app.module.ts index b11f16f7d5..bad7aa55f1 100644 --- a/static/skywire-manager-src/src/app/app.module.ts +++ b/static/skywire-manager-src/src/app/app.module.ts @@ -102,6 +102,12 @@ import { SkysocksClientSettingsComponent } from './components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component'; import { MenuButtonComponent } from './components/layout/sidenav/menu-button/menu-button.component'; +import { + EditSkysocksClientNoteComponent +} from './components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component'; +import { + SkysocksClientFilterComponent +} from './components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component'; const globalRippleConfig: RippleGlobalOptions = { disabled: true, @@ -181,6 +187,8 @@ const globalRippleConfig: RippleGlobalOptions = { SkysocksSettingsComponent, SkysocksClientSettingsComponent, MenuButtonComponent, + EditSkysocksClientNoteComponent, + SkysocksClientFilterComponent, ], entryComponents: [ ConfigurationComponent, @@ -208,6 +216,8 @@ const globalRippleConfig: RippleGlobalOptions = { TerminalComponent, SkysocksSettingsComponent, SkysocksClientSettingsComponent, + EditSkysocksClientNoteComponent, + SkysocksClientFilterComponent, ], imports: [ BrowserModule, diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.html b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.html new file mode 100644 index 0000000000..4227fff349 --- /dev/null +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.html @@ -0,0 +1,8 @@ + +
+ + + + {{ 'common.save' | translate }} +
+
diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.scss b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.spec.ts b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.spec.ts new file mode 100644 index 0000000000..f9ca3a9dd6 --- /dev/null +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditSkysocksClientNoteComponent } from './edit-skysocks-client-note.component'; + +describe('EditSkysocksClientNoteComponent', () => { + let component: EditSkysocksClientNoteComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ EditSkysocksClientNoteComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditSkysocksClientNoteComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.ts b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.ts new file mode 100644 index 0000000000..e69b246ecd --- /dev/null +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/edit-skysocks-client-note/edit-skysocks-client-note.component.ts @@ -0,0 +1,54 @@ +import { Component, ViewChild, ElementRef, OnInit, Inject } from '@angular/core'; +import { MatDialogRef, MatDialogConfig, MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { FormGroup, FormBuilder } from '@angular/forms'; + +import { AppConfig } from 'src/app/app.config'; + +/** + * Modal window for changing the note of an entry shown on SkysocksClientSettingsComponent. + * If the user selects the option for saving the note, the modal window is closed and the new + * note is returned in the "afterClosed" envent, but with an hyphen "-" added to the begining, + * to help avoiding problems while checking empty strings. + */ +@Component({ + selector: 'app-edit-skysocks-client-note', + templateUrl: './edit-skysocks-client-note.component.html', + styleUrls: ['./edit-skysocks-client-note.component.scss'] +}) +export class EditSkysocksClientNoteComponent implements OnInit { + @ViewChild('firstInput', { static: false }) firstInput: ElementRef; + + form: FormGroup; + + /** + * Opens the modal window. Please use this function instead of opening the window "by hand". + */ + public static openDialog(dialog: MatDialog, currentNote: string): MatDialogRef { + const config = new MatDialogConfig(); + config.data = currentNote ? currentNote : ''; + config.autoFocus = false; + config.width = AppConfig.smallModalWidth; + + return dialog.open(EditSkysocksClientNoteComponent, config); + } + + constructor( + private dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) private data: string, + private formBuilder: FormBuilder, + ) { } + + ngOnInit() { + this.form = this.formBuilder.group({ + 'note': [this.data], + }); + + setTimeout(() => (this.firstInput.nativeElement as HTMLElement).focus()); + } + + // Closes the modal window and returns the note. + finish() { + const note = this.form.get('note').value.trim(); + this.dialogRef.close('-' + note); + } +} diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.html b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.html new file mode 100644 index 0000000000..29f6885608 --- /dev/null +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.html @@ -0,0 +1,42 @@ + + +
+ + + {{ filter.text | translate }} + + + + + + + + + + + + + + {{ 'apps.skysocks-client-settings.filter-dialog.apply' | translate }} + +
+
diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.scss b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.spec.ts b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.spec.ts new file mode 100644 index 0000000000..c8556b1cd7 --- /dev/null +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SkysocksClientFilterComponent } from './skysocks-client-filter.component'; + +describe('SkysocksClientFilterComponent', () => { + let component: SkysocksClientFilterComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SkysocksClientFilterComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SkysocksClientFilterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.ts b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.ts new file mode 100644 index 0000000000..1de59ce52e --- /dev/null +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.ts @@ -0,0 +1,118 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { MAT_DIALOG_DATA, MatDialogRef, MatDialog, MatDialogConfig } from '@angular/material/dialog'; + +import { AppConfig } from 'src/app/app.config'; + +/** + * Options for filtering by state. + */ +export enum StateFilterStates { + NoFilter = 1, + Available = 2, + Offline = 3, +} + +/** + * Data for displaying the options for filtering by state. + */ +export interface StateFilter { + /** + * Text of the option, for using with the "translate" pipe. + */ + text: string; + /** + * Value of the option. + */ + state: StateFilterStates; +} + +/** + * Filters the user selected using SkysocksClientFilterComponent. It is prepopulated with default + * data which indicates that no filter has been selected. + */ +export class SkysocksClientFilters { + // Texts of the options for filtering by state. + static readonly stateTexts = [ + 'apps.skysocks-client-settings.filter-dialog.state-no-filter', + 'apps.skysocks-client-settings.state-available', + 'apps.skysocks-client-settings.state-offline', + ]; + + + state: StateFilter = { + text: SkysocksClientFilters.stateTexts[0], + state: StateFilterStates.NoFilter + }; + location = ''; + key = ''; +} + +/** + * Modal window for selecting the filters for the proxy list shown by + * SkysocksClientSettingsComponent. If the user accepts the changes, the modal window is closed + * and an instance of SkysocksClientFilters is returned in the "afterClosed" envent, with the + * selected filters. + */ +@Component({ + selector: 'app-skysocks-client-filter', + templateUrl: './skysocks-client-filter.component.html', + styleUrls: ['./skysocks-client-filter.component.scss'] +}) +export class SkysocksClientFilterComponent implements OnInit { + // Array with the options for filtering by state. + stateFilters: StateFilter[]; + form: FormGroup; + + /** + * Opens the modal window. Please use this function instead of opening the window "by hand". + */ + public static openDialog(dialog: MatDialog, currentFilters: SkysocksClientFilters): MatDialogRef { + const config = new MatDialogConfig(); + config.data = currentFilters; + config.autoFocus = false; + config.width = AppConfig.smallModalWidth; + + return dialog.open(SkysocksClientFilterComponent, config); + } + + constructor( + @Inject(MAT_DIALOG_DATA) private data: SkysocksClientFilters, + private dialogRef: MatDialogRef, + private formBuilder: FormBuilder, + ) { } + + ngOnInit() { + this.stateFilters = [ + { + text: SkysocksClientFilters.stateTexts[0], + state: StateFilterStates.NoFilter + }, + { + text: SkysocksClientFilters.stateTexts[1], + state: StateFilterStates.Available + }, + { + text: SkysocksClientFilters.stateTexts[2], + state: StateFilterStates.Offline + } + ]; + + this.form = this.formBuilder.group({ + 'state': [this.stateFilters[this.data.state.state - 1]], + 'location-text': [this.data.location], + 'key-text': [this.data.key], + }); + } + + // Closes the modal window and returns the selected filters. + apply() { + const response = new SkysocksClientFilters(); + + response.state = this.form.get('state').value; + response.location = (this.form.get('location-text').value as string).trim(); + response.key = (this.form.get('key-text').value as string).trim(); + + this.dialogRef.close(response); + } +} diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.html b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.html index 7a0245b33b..8d526a1415 100644 --- a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.html +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.html @@ -34,27 +34,158 @@ + + + + + +
+ error + {{ 'apps.skysocks-client-settings.no-elements' | translate }} +
+ + + + + + +
+ error + {{ 'apps.skysocks-client-settings.no-elements-for-filters' | translate }} +
+ + + + + +
+
-
+
error {{ 'apps.skysocks-client-settings.no-history' | translate:{number: maxHistoryElements} }}
-
+ + + + + + + +
+ +
+
+ {{ 'apps.skysocks-client-settings.key' | translate }} + {{ entry.key }} +
+
+ {{ 'apps.skysocks-client-settings.note' | translate }} + + {{ entry.note }} + + + + {{ 'apps.skysocks-client-settings.note-entered-manually' | translate }} + + + {{ 'apps.skysocks-client-settings.note-obtained' | translate }} + + ({{ entry.location }}) + + + +
+
+ +
+ + +
+ add +
+
+
+
diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss index 7249a87014..eeaf745912 100644 --- a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss @@ -4,7 +4,7 @@ form { margin-top: 15px; } -.no-history-text { +.info-text { margin-top: 20px; margin-bottom: 2px; text-align: center; @@ -16,12 +16,78 @@ form { } } -.top-history-margin { - width: 100%; - height: 15px; +.loading-indicator { + height: 100px; } -.history-button-content { - text-align: left; - padding: 5px 0px; +.list-button { + border-bottom: solid 1px $separator; + + .filter-button-content { + padding: 15px 0px; + white-space: normal; + line-height: 1.3; + color: $black; + text-align: left; + display: flex; + font-size: $font-size-smaller; + word-break: break-word; + + .icon-area { + font-size: 20px; + margin-right: 15px; + color: $lighter-gray; + opacity: 0.4; + align-self: center; + } + + .item { + margin: 4px 0px; + + span:first-of-type { + color: $lighter-gray; + } + } + + .blue-part { + color: $blue; + } + } + + .button-content { + text-align: left; + padding: 15px 0px; + white-space: normal; + + .full-size-area { + flex-grow: 1; + } + + .item { + line-height: 1.3; + margin: 4px 0px; + font-size: $font-size-smaller; + color: $black; + word-break: break-all; + + span:first-of-type { + color: $lighter-gray; + } + } + + .options-container { + flex-shrink: 0; + margin-left: 5px; + text-align: right; + line-height: 1; + + .small-button { + width: 24px; + height: 24px; + line-height: 14px; + font-size: 14px; + margin-left: 5px; + } + } + } } diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts index ae0986b422..34c2085d4b 100644 --- a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts @@ -11,7 +11,38 @@ 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'; -import { Application } from 'src/app/app.datatypes'; +import { Application, ProxyDiscoveryEntry } from 'src/app/app.datatypes'; +import { ProxyDiscoveryService } from 'src/app/services/proxy-discovery.service'; +import { EditSkysocksClientNoteComponent } from './edit-skysocks-client-note/edit-skysocks-client-note.component'; +import { SelectableOption, SelectOptionComponent } from 'src/app/components/layout/select-option/select-option.component'; +import { + SkysocksClientFilterComponent, + SkysocksClientFilters, + StateFilterStates +} from './skysocks-client-filter/skysocks-client-filter.component'; + +/** + * Data of the entries from the history. + */ +export interface HistoryEntry { + /** + * Remote public key. + */ + key: string; + /** + * If true, the user entered the data manually using the form. If false, the data was obtained + * from the discovery service. + */ + enteredManually: boolean; + /** + * Location of the visor. Only if it was obtained from the discovery service. + */ + location?: string; + /** + * Custom note added by the user. + */ + note?: string; +} /** * Modal window used for configuring the Skysocks-client app. @@ -23,7 +54,7 @@ import { Application } from 'src/app/app.datatypes'; }) export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { // Key for saving the history in persistent storage. - private readonly historyStorageKey = 'SkysocksClientHistory'; + private readonly historyStorageKey = 'SkysocksClientHistory_'; // Max elements the history can contain. readonly maxHistoryElements = 10; @@ -31,13 +62,29 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { @ViewChild('firstInput', { static: false }) firstInput: ElementRef; form: FormGroup; // Entries to show on the history. - history: string[]; + history: HistoryEntry[]; - // If the operation in being currently made. + // Proxies obtained from the discovery service. + proxiesFromDiscovery: ProxyDiscoveryEntry[]; + // Filtered proxies. + proxiesFromDiscoveryToShow: ProxyDiscoveryEntry[]; + // If the system is still getting the proxies from the discovery service. + loadingFromDiscovery = true; + + // Current filters for the poxies from the discovery service. + currentFilters = new SkysocksClientFilters(); + // Texts to be shown on the filter button. Each element represents a filter and has 3 + // elements. The fist one is a translatable var which describes the filter, the second one has + // the value selected by the user if it is a variable for the translate pipe and the third one + // has the value selected by the user if the translate pipe is not needed, + currentFiltersTexts: string[][] = []; + + stateFilterStates = StateFilterStates; + + // If the operation in currently being made. private working = false; - // Last public key set to be sent to the backend. - private lastPublicKey: string; private operationSubscription: Subscription; + private discoverySubscription: Subscription; /** * Opens the modal window. Please use this function instead of opening the window "by hand". @@ -46,7 +93,7 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { const config = new MatDialogConfig(); config.data = app; config.autoFocus = false; - config.width = AppConfig.mediumModalWidth; + config.width = AppConfig.largeModalWidth; return dialog.open(SkysocksClientSettingsComponent, config); } @@ -58,9 +105,17 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { private formBuilder: FormBuilder, private snackbarService: SnackbarService, private dialog: MatDialog, + private proxyDiscoveryService: ProxyDiscoveryService, ) { } ngOnInit() { + // Get the proxies from the discovery service. + this.discoverySubscription = this.proxyDiscoveryService.getProxies().subscribe(response => { + this.proxiesFromDiscovery = response; + this.filterProxies(); + this.loadingFromDiscovery = false; + }); + // Get the history. const retrievedHistory = localStorage.getItem(this.historyStorageKey); if (retrievedHistory) { @@ -92,31 +147,164 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { } ngOnDestroy() { + this.discoverySubscription.unsubscribe(); if (this.operationSubscription) { this.operationSubscription.unsubscribe(); } } + // Opens the modal window for selecting the filters. + changeFilters() { + SkysocksClientFilterComponent.openDialog(this.dialog, this.currentFilters).afterClosed().subscribe(response => { + if (response) { + this.currentFilters = response; + this.filterProxies(); + } + }); + } + + // Filters the proxies obtained from the discovery service using the filters selected by + // the user. + private filterProxies() { + if (this.currentFilters.state.state === StateFilterStates.NoFilter && !this.currentFilters.location && !this.currentFilters.key) { + this.proxiesFromDiscoveryToShow = this.proxiesFromDiscovery; + } else { + this.proxiesFromDiscoveryToShow = this.proxiesFromDiscovery.filter(proxy => { + if (this.currentFilters.state.state === StateFilterStates.Available && !proxy.available) { + return false; + } + if (this.currentFilters.state.state === StateFilterStates.Offline && proxy.available) { + return false; + } + if (this.currentFilters.location && !proxy.location.toLowerCase().includes(this.currentFilters.location.toLowerCase())) { + return false; + } + if (this.currentFilters.key && !proxy.publicKeyPort.toLowerCase().includes(this.currentFilters.key.toLowerCase())) { + return false; + } + + return true; + }); + } + + this.updateCurrentFilters(); + } + + // Updates the texts of the filter button. + private updateCurrentFilters() { + this.currentFiltersTexts = []; + + if (this.currentFilters.state.state !== StateFilterStates.NoFilter) { + this.currentFiltersTexts.push(['apps.skysocks-client-settings.filter-dialog.state', this.currentFilters.state.text, '']); + } + if (this.currentFilters.location) { + this.currentFiltersTexts.push(['apps.skysocks-client-settings.filter-dialog.location', '', this.currentFilters.location]); + } + if (this.currentFilters.key) { + this.currentFiltersTexts.push(['apps.skysocks-client-settings.filter-dialog.pub-key', '', this.currentFilters.key]); + } + } + + // Opens the modal window used on small screens with the options of an history entry. + openHistoryOptions(historyEntry: HistoryEntry) { + const options: SelectableOption[] = [ + { + icon: 'chevron_right', + label: 'apps.skysocks-client-settings.use', + }, + { + icon: 'edit', + label: 'apps.skysocks-client-settings.change-note', + }, + { + icon: 'close', + label: 'apps.skysocks-client-settings.remove-entry', + } + ]; + + SelectOptionComponent.openDialog(this.dialog, options).afterClosed().subscribe((selectedOption: number) => { + if (selectedOption === 1) { + this.saveChanges(historyEntry.key, historyEntry.enteredManually, historyEntry.location, historyEntry.note); + } else if (selectedOption === 2) { + this.changeNote(historyEntry); + } else if (selectedOption === 3) { + this.removeFromHistory(historyEntry.key); + } + }); + } + + // Removes an element from the history. + removeFromHistory(key: String) { + // Ask for confirmation. + const confirmationMsg = 'apps.skysocks-client-settings.remove-from-history-confirmation'; + const confirmationDialog = GeneralUtils.createConfirmationDialog(this.dialog, confirmationMsg); + + confirmationDialog.componentInstance.operationAccepted.subscribe(() => { + this.history = this.history.filter(value => value.key !== key); + const dataToSave = JSON.stringify(this.history); + localStorage.setItem(this.historyStorageKey, dataToSave); + + confirmationDialog.close(); + }); + } + + // Opens the modal window for changing the personal note of an history entry. + changeNote(entry: HistoryEntry) { + EditSkysocksClientNoteComponent.openDialog(this.dialog, entry.note).afterClosed().subscribe((response: string) => { + if (response) { + // Remove the "-" char the modal window adds at the start of the note. + response = response.substr(1, response.length - 1); + + // Change the note. + this.history.forEach(value => { + if (value.key === entry.key) { + value.note = response; + } + }); + + // Save the changes.. + const dataToSave = JSON.stringify(this.history); + localStorage.setItem(this.historyStorageKey, dataToSave); + + if (!response) { + this.snackbarService.showWarning('apps.skysocks-client-settings.default-note-warning'); + } else { + this.snackbarService.showDone('apps.skysocks-client-settings.changes-made'); + } + } + }); + } + /** - * Saves the settings. + * Saves the settings. If no argument is provided, the function will take the public key + * from the form and fill the rest of the data. The arguments are mainly for proxies selected + * from the discovery list and entries from the history. + * @param publicKey New public key to be used. + * @param enteredManually If the user manually entered the data using the form. + * @param location Location of the proxy server. + * @param note Personal note for the history. */ - saveChanges(publicKey: string = null) { + saveChanges(publicKey: string = null, enteredManually: boolean = null, location: string = null, note: string = null) { + // If no public key was provided, the data will be retrieved from the form, so the form + // must be valid. Also, the operation can not continue if the component is already working. if ((!this.form.valid && !publicKey) || this.working) { return; } - this.lastPublicKey = publicKey ? publicKey : this.form.get('pk').value; + enteredManually = publicKey ? enteredManually : true; + publicKey = 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(); + this.continueSavingChanges(publicKey, enteredManually, location, note); }); } - private continueSavingChanges() { + // Makes the call to the hypervisor API for changing the configuration. + private continueSavingChanges(publicKey: string, enteredManually: boolean, location: string, note: string) { this.button.showLoading(); this.working = true; @@ -124,19 +312,31 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { // The node pk is obtained from the currently openned node page. NodeComponent.getCurrentNodeKey(), this.data.name, - { pk: this.lastPublicKey }, - ).subscribe({ - next: this.onSuccess.bind(this), - error: this.onError.bind(this) - }); + { pk: publicKey }, + ).subscribe( + () => this.onSuccess(publicKey, enteredManually, location, note), + err => this.onError(err), + ); } - private onSuccess() { + private onSuccess(publicKey: string, enteredManually: boolean, location: string, note: string) { // Remove any repeated entry from the history. - this.history = this.history.filter(value => value !== this.lastPublicKey); + this.history = this.history.filter(value => value.key !== publicKey); + + // Add the available data to the history entry. + const newEntry: HistoryEntry = { + key: publicKey, + enteredManually: enteredManually, + }; + if (location) { + newEntry.location = location; + } + if (note) { + newEntry.note = note; + } - // Save the new public key on the history. - this.history = [this.lastPublicKey].concat(this.history); + // Save the data on the history. + this.history = [newEntry].concat(this.history); if (this.history.length > this.maxHistoryElements) { const itemsToRemove = this.history.length - this.maxHistoryElements; this.history.splice(this.history.length - itemsToRemove, itemsToRemove); diff --git a/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts b/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts new file mode 100644 index 0000000000..3ad7075393 --- /dev/null +++ b/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts @@ -0,0 +1,62 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { retryWhen, delay, map } from 'rxjs/operators'; + +import { ProxyDiscoveryEntry } from '../app.datatypes'; + +/** + * Allows to get the proxies registered in the proxy discovery service. + */ +@Injectable({ + providedIn: 'root' +}) +export class ProxyDiscoveryService { + /** + * URL of the proxy discovery service. + */ + private readonly discoveryServiceUrl = 'http://localhost:8081'; + + constructor( + private http: HttpClient, + ) {} + + /** + * Get the proxies registered in the proxy discovery service. + */ + getProxies(): Observable { + return this.http.get(this.discoveryServiceUrl + '/api/v1/getAll').pipe( + // In case of error, retry. + retryWhen(errors => errors.pipe(delay(4000))), + map((response: ProxyDiscoveryEntry[]) => { + // Process the data. + response.forEach(proxy => { + // Remove the invalid dates. + if (proxy.updatedAt) { + proxy.updatedAt = proxy.updatedAt.startsWith('0001-01-01') ? null : proxy.updatedAt; + } + + // Process the status. + if (proxy.status) { + proxy.available = proxy.status.toLowerCase() === 'available'; + } + + // Process the location. + let location = ''; + if (proxy.city) { + location += proxy.city; + } + if (proxy.city && proxy.country) { + location += ', '; + } + if (proxy.country) { + location += proxy.country; + } + proxy.location = location; + }); + + return response; + }) + ); + } +} diff --git a/static/skywire-manager-src/src/assets/i18n/en.json b/static/skywire-manager-src/src/assets/i18n/en.json index 7929c82b97..6d4d4933f0 100644 --- a/static/skywire-manager-src/src/assets/i18n/en.json +++ b/static/skywire-manager-src/src/assets/i18n/en.json @@ -302,15 +302,48 @@ }, "skysocks-client-settings": { "title": "Skysocks-Client Settings", - "remote-visor-tab": "Remote Visor", + "discovery-tab": "Search visor", + "remote-visor-tab": "Enter manually", "history-tab": "History", + "use": "Use this data", + "change-note": "Change note", + "remove-entry": "Remove entry", + "note": "Note:", + "note-entered-manually": "Entered manually", + "note-obtained": "Obtained from the discovery service", + "key": "Key:", + "location": "Location:", + "state": "State:", + "state-available": "Available", + "state-offline": "Offline", + "update": "Last updated:", "public-key": "Remote visor public key", + "no-elements": "Currently there are no elements to show. Please try again later.", + "no-elements-for-filters": "There are no elements that meet the filter criteria.", + "no-filter": "No filter has been selected", + "click-to-change": "Click to change", "remote-key-length-error": "The public key must be 66 characters long.", "remote-key-chars-error": "The public key must only contain hexadecimal characters.", "save": "Save", + "remove-from-history-confirmation": "Are you sure you want to remove the entry from the history?", "change-key-confirmation": "Are you sure you want to change the remote visor public key?", "changes-made": "The changes have been made.", - "no-history": "This tab will show the last {{ number }} public keys used." + "no-history": "This tab will show the last {{ number }} public keys used.", + "default-note-warning": "The default note has been used.", + + "change-note-dialog": { + "title": "Change Note", + "note": "Note" + }, + + "filter-dialog": { + "title": "Filters", + "state": "The state must be", + "state-no-filter": "Do not filter", + "location": "The location must contain", + "pub-key": "The public key must contain", + "apply": "Apply" + } }, "stop-app": "Stop", "start-app": "Start", diff --git a/static/skywire-manager-src/src/assets/i18n/es.json b/static/skywire-manager-src/src/assets/i18n/es.json index 462944a3d6..62c37b86f9 100644 --- a/static/skywire-manager-src/src/assets/i18n/es.json +++ b/static/skywire-manager-src/src/assets/i18n/es.json @@ -272,15 +272,48 @@ }, "skysocks-client-settings": { "title": "Configuración de Skysocks-Client", - "remote-visor-tab": "Visor Remoto", + "discovery-tab": "Buscar visor", + "remote-visor-tab": "Introducir manualmente", "history-tab": "Historial", + "use": "Usar estos datos", + "change-note": "Cambiar nota", + "remove-entry": "Remover entrada", + "note": "Nota:", + "note-entered-manually": "Introducido manualmente", + "note-obtained": "Obtenido del servicio de descubrimiento", + "key": "Llave:", + "location": "Ubicación:", + "state": "Estado:", + "state-available": "Disponible", + "state-offline": "Offline", + "update": "Última vez actualizado:", "public-key": "Llave pública del visor remoto", + "no-elements": "Actualmente no hay elementos para mostrar. Por favor, inténtelo de nuevo más tarde.", + "no-elements-for-filters": "No hay elementos que cumplan los criterios de filtro.", + "no-filter": "No se ha seleccionado ningún filtro", + "click-to-change": "Haga clic para cambiar", "remote-key-length-error": "La llave pública debe tener 66 caracteres.", "remote-key-chars-error": "La llave pública sólo debe contener caracteres hexadecimales.", "save": "Guardar", + "remove-from-history-confirmation": "¿Seguro de que desea eliminar la entrada del historial?", "change-key-confirmation": "¿Seguro que desea cambiar la llave pública del visor remoto?", "changes-made": "Los cambios han sido realizados.", - "no-history": "Esta pestaña mostrará las últimas {{ number }} llaves públicas usadas." + "no-history": "Esta pestaña mostrará las últimas {{ number }} llaves públicas usadas.", + "default-note-warning": "La nota por defecto ha sido utilizada.", + + "change-note-dialog": { + "title": "Cambiar Nota", + "note": "Nota" + }, + + "filter-dialog": { + "title": "Filtros", + "state": "El estado debe ser", + "state-no-filter": "No filtrar", + "location": "La ubicación debe contener", + "pub-key": "La llave pública debe contener", + "apply": "Aplicar" + } }, "stop-app": "Detener", "start-app": "Iniciar", diff --git a/static/skywire-manager-src/src/assets/i18n/es_base.json b/static/skywire-manager-src/src/assets/i18n/es_base.json index a7a8bfcbdc..4018bfeb35 100644 --- a/static/skywire-manager-src/src/assets/i18n/es_base.json +++ b/static/skywire-manager-src/src/assets/i18n/es_base.json @@ -272,15 +272,48 @@ }, "skysocks-client-settings": { "title": "Skysocks-Client Settings", - "remote-visor-tab": "Remote Visor", + "discovery-tab": "Search visor", + "remote-visor-tab": "Enter manually", "history-tab": "History", + "use": "Use this data", + "change-note": "Change note", + "remove-entry": "Remove entry", + "note": "Note:", + "note-entered-manually": "Entered manually", + "note-obtained": "Obtained from the discovery service", + "key": "Key:", + "location": "Location:", + "state": "State:", + "state-available": "Available", + "state-offline": "Offline", + "update": "Last updated:", "public-key": "Remote visor public key", + "no-elements": "Currently there are no elements to show. Please try again later.", + "no-elements-for-filters": "There are no elements that meet the filter criteria.", + "no-filter": "No filter has been selected", + "click-to-change": "Click to change", "remote-key-length-error": "The public key must be 66 characters long.", "remote-key-chars-error": "The public key must only contain hexadecimal characters.", "save": "Save", + "remove-from-history-confirmation": "Are you sure you want to remove the entry from the history?", "change-key-confirmation": "Are you sure you want to change the remote visor public key?", "changes-made": "The changes have been made.", - "no-history": "This tab will show the last {{ number }} public keys used." + "no-history": "This tab will show the last {{ number }} public keys used.", + "default-note-warning": "The default note has been used.", + + "change-note-dialog": { + "title": "Change Note", + "note": "Note" + }, + + "filter-dialog": { + "title": "Filters", + "state": "The state must be", + "state-no-filter": "Do not filter", + "location": "The location must contain", + "pub-key": "The public key must contain", + "apply": "Apply" + } }, "stop-app": "Stop", "start-app": "Start", From 6a1546cf3f2e2ca93f68e5fa17720bf815adf533 Mon Sep 17 00:00:00 2001 From: Senyoret1 <34079003+Senyoret1@users.noreply.github.com> Date: Fri, 17 Apr 2020 19:22:30 -0400 Subject: [PATCH 2/6] Add pagination to the skysocks-client config in the manager --- .../skysocks-client-settings.component.html | 14 ++++- .../skysocks-client-settings.component.scss | 18 ++++++ .../skysocks-client-settings.component.ts | 60 ++++++++++++++++++- .../src/assets/i18n/en.json | 1 + .../src/assets/i18n/es.json | 1 + .../src/assets/i18n/es_base.json | 1 + 6 files changed, 92 insertions(+), 3 deletions(-) diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.html b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.html index 8d526a1415..0172361250 100644 --- a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.html +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.html @@ -70,7 +70,7 @@ -
+
error {{ 'apps.skysocks-client-settings.no-elements-for-filters' | translate }}
@@ -105,6 +105,18 @@ + + +
+ {{ 'apps.skysocks-client-settings.pagination-info' | translate:{currentElementsRange: currentRange, totalElements: filteredProxiesFromDiscovery.length} }} + + + +
diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss index eeaf745912..c920187a37 100644 --- a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss @@ -1,4 +1,5 @@ @import "variables"; +@import "bootstrap_overrides"; form { margin-top: 15px; @@ -91,3 +92,20 @@ form { } } } + +.paginator { + float: right; + margin-top: 15px; + + > span { + @media (max-width: (map-get($grid-breakpoints, md) - 1)) { + & { + font-size: $font-size-mini; + } + } + } + + button { + margin-left: 5px; + } +} diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts index 34c2085d4b..edf07188d3 100644 --- a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts @@ -57,6 +57,8 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { private readonly historyStorageKey = 'SkysocksClientHistory_'; // Max elements the history can contain. readonly maxHistoryElements = 10; + // How many elements to show per page on the proxy discovery tab. + readonly maxElementsPerPage = 10; @ViewChild('button', { static: false }) button: ButtonComponent; @ViewChild('firstInput', { static: false }) firstInput: ElementRef; @@ -67,9 +69,17 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { // Proxies obtained from the discovery service. proxiesFromDiscovery: ProxyDiscoveryEntry[]; // Filtered proxies. + filteredProxiesFromDiscovery: ProxyDiscoveryEntry[]; + // Proxies to show in the currently selected page. proxiesFromDiscoveryToShow: ProxyDiscoveryEntry[]; // If the system is still getting the proxies from the discovery service. loadingFromDiscovery = true; + // How many pages with proxies there are. + numberOfPages = 1; + // Current page. + currentPage = 1; + // Which elements are being shown in the currently selected page. + currentRange = '1 - 1'; // Current filters for the poxies from the discovery service. currentFilters = new SkysocksClientFilters(); @@ -167,9 +177,9 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { // the user. private filterProxies() { if (this.currentFilters.state.state === StateFilterStates.NoFilter && !this.currentFilters.location && !this.currentFilters.key) { - this.proxiesFromDiscoveryToShow = this.proxiesFromDiscovery; + this.filteredProxiesFromDiscovery = this.proxiesFromDiscovery; } else { - this.proxiesFromDiscoveryToShow = this.proxiesFromDiscovery.filter(proxy => { + this.filteredProxiesFromDiscovery = this.proxiesFromDiscovery.filter(proxy => { if (this.currentFilters.state.state === StateFilterStates.Available && !proxy.available) { return false; } @@ -188,6 +198,7 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { } this.updateCurrentFilters(); + this.updatePagination(); } // Updates the texts of the filter button. @@ -205,6 +216,51 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { } } + // Updates the vars related to the pagination of the proxy discovery tab and shows + // the first page. + private updatePagination() { + this.currentPage = 1; + this.numberOfPages = Math.ceil(this.filteredProxiesFromDiscovery.length / this.maxElementsPerPage); + this.showCurrentPage(); + } + + // Goes to the next page in the proxy discovery tab. + goToNextPage() { + if (this.currentPage >= this.numberOfPages) { + return; + } + + this.currentPage += 1; + this.showCurrentPage(); + } + + // Goes to the previous page in the proxy discovery tab. + goToPreviousPage() { + if (this.currentPage <= 1) { + return; + } + + this.currentPage -= 1; + this.showCurrentPage(); + } + + // Updates the UI to show the elements of the page indicated in the currentPage var. + private showCurrentPage() { + // Update the elements to show. + this.proxiesFromDiscoveryToShow = this.filteredProxiesFromDiscovery.slice( + (this.currentPage - 1) * this.maxElementsPerPage, + this.currentPage * this.maxElementsPerPage + ); + + // Update the text with the range currently shown. + this.currentRange = (((this.currentPage - 1) * this.maxElementsPerPage) + 1) + ' - '; + if (this.currentPage < this.numberOfPages) { + this.currentRange += (this.currentPage * this.maxElementsPerPage) + ''; + } else { + this.currentRange += this.filteredProxiesFromDiscovery.length + ''; + } + } + // Opens the modal window used on small screens with the options of an history entry. openHistoryOptions(historyEntry: HistoryEntry) { const options: SelectableOption[] = [ diff --git a/static/skywire-manager-src/src/assets/i18n/en.json b/static/skywire-manager-src/src/assets/i18n/en.json index 6d4d4933f0..babbfe4c1e 100644 --- a/static/skywire-manager-src/src/assets/i18n/en.json +++ b/static/skywire-manager-src/src/assets/i18n/en.json @@ -330,6 +330,7 @@ "changes-made": "The changes have been made.", "no-history": "This tab will show the last {{ number }} public keys used.", "default-note-warning": "The default note has been used.", + "pagination-info": "{{ currentElementsRange }} of {{ totalElements }}", "change-note-dialog": { "title": "Change Note", diff --git a/static/skywire-manager-src/src/assets/i18n/es.json b/static/skywire-manager-src/src/assets/i18n/es.json index 62c37b86f9..0debda68f9 100644 --- a/static/skywire-manager-src/src/assets/i18n/es.json +++ b/static/skywire-manager-src/src/assets/i18n/es.json @@ -300,6 +300,7 @@ "changes-made": "Los cambios han sido realizados.", "no-history": "Esta pestaña mostrará las últimas {{ number }} llaves públicas usadas.", "default-note-warning": "La nota por defecto ha sido utilizada.", + "pagination-info": "{{ currentElementsRange }} de {{ totalElements }}", "change-note-dialog": { "title": "Cambiar Nota", diff --git a/static/skywire-manager-src/src/assets/i18n/es_base.json b/static/skywire-manager-src/src/assets/i18n/es_base.json index 4018bfeb35..68c4a83b60 100644 --- a/static/skywire-manager-src/src/assets/i18n/es_base.json +++ b/static/skywire-manager-src/src/assets/i18n/es_base.json @@ -300,6 +300,7 @@ "changes-made": "The changes have been made.", "no-history": "This tab will show the last {{ number }} public keys used.", "default-note-warning": "The default note has been used.", + "pagination-info": "{{ currentElementsRange }} of {{ totalElements }}", "change-note-dialog": { "title": "Change Note", From c81d8c4846bda198fbd1723aae8260fb023d308c Mon Sep 17 00:00:00 2001 From: Senyoret1 <34079003+Senyoret1@users.noreply.github.com> Date: Wed, 29 Apr 2020 15:26:22 -0400 Subject: [PATCH 3/6] Improvements for the proxy discovery UI on the manager --- static/skywire-manager-src/proxy.config.json | 8 +++ .../src/app/app.datatypes.ts | 9 ++- .../skysocks-client-filter.component.html | 9 --- .../skysocks-client-filter.component.ts | 54 ---------------- .../skysocks-client-settings.component.html | 24 ++++--- .../skysocks-client-settings.component.scss | 6 +- .../skysocks-client-settings.component.ts | 63 ++++++++++++++----- .../app/services/proxy-discovery.service.ts | 60 +++++++++++------- .../src/assets/i18n/en.json | 5 +- .../src/assets/i18n/es.json | 5 +- .../src/assets/i18n/es_base.json | 5 +- 11 files changed, 115 insertions(+), 133 deletions(-) diff --git a/static/skywire-manager-src/proxy.config.json b/static/skywire-manager-src/proxy.config.json index 4d59f228d0..3b26a04b5a 100644 --- a/static/skywire-manager-src/proxy.config.json +++ b/static/skywire-manager-src/proxy.config.json @@ -11,5 +11,13 @@ "pathRewrite": { "^/http-api" : "/api" } + }, + "/discovery-api": { + "target": "http://127.0.0.1:8001", + "secure": false, + "changeOrigin": true, + "pathRewrite": { + "^/discovery-api" : "/api" + } } } diff --git a/static/skywire-manager-src/src/app/app.datatypes.ts b/static/skywire-manager-src/src/app/app.datatypes.ts index a3734ad498..e246d57c57 100644 --- a/static/skywire-manager-src/src/app/app.datatypes.ts +++ b/static/skywire-manager-src/src/app/app.datatypes.ts @@ -55,13 +55,12 @@ export interface HealthInfo { } export class ProxyDiscoveryEntry { - publicKeyPort: string; + address: string; + pk: string; + port: string; country?: string; - city?: string; + region?: string; location?: string; - status?: string; - available?: boolean; - updatedAt?: string; } // old diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.html b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.html index 29f6885608..07f33236e1 100644 --- a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.html +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-filter/skysocks-client-filter.component.html @@ -1,14 +1,5 @@ -
- - - {{ filter.text | translate }} - -
{{ 'apps.skysocks-client-settings.key' | translate }} - {{ proxy.publicKeyPort }} +   + {{ part }} + +
+
+ {{ 'apps.skysocks-client-settings.port' | translate }} + {{ proxy.port }}
{{ 'apps.skysocks-client-settings.location' | translate }} - {{ proxy.location }} -
-
- {{ 'apps.skysocks-client-settings.state' | translate }} - - {{ ('apps.skysocks-client-settings.state-' + (proxy.available ? 'available' : 'offline' )) | translate }} - -
-
- {{ 'apps.skysocks-client-settings.update' | translate }} - {{ proxy.updatedAt }} +   + {{ part }} +
diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss index c920187a37..161f4fa11d 100644 --- a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss @@ -71,9 +71,13 @@ form { color: $black; word-break: break-all; - span:first-of-type { + > span:first-of-type { color: $lighter-gray; } + + .highlighted { + background-color: yellow; + } } .options-container { diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts index edf07188d3..4c796de562 100644 --- a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts @@ -17,8 +17,7 @@ import { EditSkysocksClientNoteComponent } from './edit-skysocks-client-note/edi import { SelectableOption, SelectOptionComponent } from 'src/app/components/layout/select-option/select-option.component'; import { SkysocksClientFilterComponent, - SkysocksClientFilters, - StateFilterStates + SkysocksClientFilters } from './skysocks-client-filter/skysocks-client-filter.component'; /** @@ -89,8 +88,6 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { // has the value selected by the user if the translate pipe is not needed, currentFiltersTexts: string[][] = []; - stateFilterStates = StateFilterStates; - // If the operation in currently being made. private working = false; private operationSubscription: Subscription; @@ -173,23 +170,62 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { }); } + /** + * Returns an array that can be used to highlight a filter term in a string. No html tags + * are returned to avoid security problems. + * @param completeText Text string where the filter term must be highlighted. + * @param filter term used for filtering the list. + * @returns An array in which the value of completeText has been divided. Each even element + * has a part of the text which must NOT be highlighted and each odd element has a part + * which must be highlighted. + */ + getHighlightedTextParts(completeText: string, filter: string): string[] { + if (!filter) { + return [completeText]; + } + + // Lowercase version for comparations. + const lowercaseCompleteText = completeText.toLowerCase(); + const lowercaseFilter = filter.toLowerCase(); + + let process = true; + let currentIndex = 0; + + const response: string[] = []; + + while (process) { + // Get the next part where the filter term is. + const index = lowercaseCompleteText.indexOf(lowercaseFilter, currentIndex); + + if (index === -1) { + process = false; + } else { + // Include the part which is before the term. + response.push(completeText.substring(currentIndex, index)); + // Include the term as it is in the original string. + response.push(completeText.substring(index, index + filter.length)); + + currentIndex = index + filter.length; + } + } + + // Add the rest of the text. + response.push(completeText.substring(currentIndex)); + + return response; + } + // Filters the proxies obtained from the discovery service using the filters selected by // the user. private filterProxies() { - if (this.currentFilters.state.state === StateFilterStates.NoFilter && !this.currentFilters.location && !this.currentFilters.key) { + if (!this.currentFilters.location && !this.currentFilters.key) { this.filteredProxiesFromDiscovery = this.proxiesFromDiscovery; } else { this.filteredProxiesFromDiscovery = this.proxiesFromDiscovery.filter(proxy => { - if (this.currentFilters.state.state === StateFilterStates.Available && !proxy.available) { - return false; - } - if (this.currentFilters.state.state === StateFilterStates.Offline && proxy.available) { - return false; - } if (this.currentFilters.location && !proxy.location.toLowerCase().includes(this.currentFilters.location.toLowerCase())) { return false; } - if (this.currentFilters.key && !proxy.publicKeyPort.toLowerCase().includes(this.currentFilters.key.toLowerCase())) { + if (this.currentFilters.key && !proxy.address.toLowerCase().includes(this.currentFilters.key.toLowerCase())) { return false; } @@ -205,9 +241,6 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { private updateCurrentFilters() { this.currentFiltersTexts = []; - if (this.currentFilters.state.state !== StateFilterStates.NoFilter) { - this.currentFiltersTexts.push(['apps.skysocks-client-settings.filter-dialog.state', this.currentFilters.state.text, '']); - } if (this.currentFilters.location) { this.currentFiltersTexts.push(['apps.skysocks-client-settings.filter-dialog.location', '', this.currentFilters.location]); } diff --git a/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts b/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts index 3ad7075393..63b7bdb2da 100644 --- a/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts +++ b/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts @@ -4,6 +4,7 @@ import { HttpClient } from '@angular/common/http'; import { retryWhen, delay, map } from 'rxjs/operators'; import { ProxyDiscoveryEntry } from '../app.datatypes'; +import { environment } from 'src/environments/environment'; /** * Allows to get the proxies registered in the proxy discovery service. @@ -13,9 +14,10 @@ import { ProxyDiscoveryEntry } from '../app.datatypes'; }) export class ProxyDiscoveryService { /** - * URL of the proxy discovery service. + * URL of the proxy discovery service. While in dev mode the url is managed by the + * dev server proxy. */ - private readonly discoveryServiceUrl = 'http://localhost:8081'; + private readonly discoveryServiceUrl = environment.production ? 'http://proxy.discovery.skywire.cc/api' : '/discovery-api'; constructor( private http: HttpClient, @@ -25,34 +27,44 @@ export class ProxyDiscoveryService { * Get the proxies registered in the proxy discovery service. */ getProxies(): Observable { - return this.http.get(this.discoveryServiceUrl + '/api/v1/getAll').pipe( + const response: ProxyDiscoveryEntry[] = []; + + return this.http.get(this.discoveryServiceUrl + '/proxies').pipe( // In case of error, retry. retryWhen(errors => errors.pipe(delay(4000))), - map((response: ProxyDiscoveryEntry[]) => { + map((result: any[]) => { // Process the data. - response.forEach(proxy => { - // Remove the invalid dates. - if (proxy.updatedAt) { - proxy.updatedAt = proxy.updatedAt.startsWith('0001-01-01') ? null : proxy.updatedAt; - } + result.forEach(proxy => { + const currentEntry = new ProxyDiscoveryEntry(); - // Process the status. - if (proxy.status) { - proxy.available = proxy.status.toLowerCase() === 'available'; - } + // The address must have 2 parts: the pk and the port. + const addressParts = (proxy.address as string).split(':'); + if (addressParts.length === 2) { + currentEntry.address = proxy.address; + currentEntry.pk = addressParts[0]; + currentEntry.port = addressParts[1]; - // Process the location. - let location = ''; - if (proxy.city) { - location += proxy.city; - } - if (proxy.city && proxy.country) { - location += ', '; - } - if (proxy.country) { - location += proxy.country; + currentEntry.location = ''; + + // Process the location. + if (proxy.geo) { + if (proxy.geo.region) { + currentEntry.region = proxy.geo.region; + currentEntry.location += currentEntry.region; + } + + if (proxy.geo.region && proxy.geo.country) { + currentEntry.location += ', '; + } + + if (proxy.geo.country) { + currentEntry.country = proxy.geo.country; + currentEntry.location += currentEntry.country; + } + } + + response.push(currentEntry); } - proxy.location = location; }); return response; diff --git a/static/skywire-manager-src/src/assets/i18n/en.json b/static/skywire-manager-src/src/assets/i18n/en.json index babbfe4c1e..720fd5d987 100644 --- a/static/skywire-manager-src/src/assets/i18n/en.json +++ b/static/skywire-manager-src/src/assets/i18n/en.json @@ -312,11 +312,10 @@ "note-entered-manually": "Entered manually", "note-obtained": "Obtained from the discovery service", "key": "Key:", + "port": "Port:", "location": "Location:", - "state": "State:", "state-available": "Available", "state-offline": "Offline", - "update": "Last updated:", "public-key": "Remote visor public key", "no-elements": "Currently there are no elements to show. Please try again later.", "no-elements-for-filters": "There are no elements that meet the filter criteria.", @@ -339,8 +338,6 @@ "filter-dialog": { "title": "Filters", - "state": "The state must be", - "state-no-filter": "Do not filter", "location": "The location must contain", "pub-key": "The public key must contain", "apply": "Apply" diff --git a/static/skywire-manager-src/src/assets/i18n/es.json b/static/skywire-manager-src/src/assets/i18n/es.json index 0debda68f9..927e34494b 100644 --- a/static/skywire-manager-src/src/assets/i18n/es.json +++ b/static/skywire-manager-src/src/assets/i18n/es.json @@ -282,11 +282,10 @@ "note-entered-manually": "Introducido manualmente", "note-obtained": "Obtenido del servicio de descubrimiento", "key": "Llave:", + "port": "Puerto:", "location": "Ubicación:", - "state": "Estado:", "state-available": "Disponible", "state-offline": "Offline", - "update": "Última vez actualizado:", "public-key": "Llave pública del visor remoto", "no-elements": "Actualmente no hay elementos para mostrar. Por favor, inténtelo de nuevo más tarde.", "no-elements-for-filters": "No hay elementos que cumplan los criterios de filtro.", @@ -309,8 +308,6 @@ "filter-dialog": { "title": "Filtros", - "state": "El estado debe ser", - "state-no-filter": "No filtrar", "location": "La ubicación debe contener", "pub-key": "La llave pública debe contener", "apply": "Aplicar" diff --git a/static/skywire-manager-src/src/assets/i18n/es_base.json b/static/skywire-manager-src/src/assets/i18n/es_base.json index 68c4a83b60..4a634fc5f8 100644 --- a/static/skywire-manager-src/src/assets/i18n/es_base.json +++ b/static/skywire-manager-src/src/assets/i18n/es_base.json @@ -282,11 +282,10 @@ "note-entered-manually": "Entered manually", "note-obtained": "Obtained from the discovery service", "key": "Key:", + "port": "Port:", "location": "Location:", - "state": "State:", "state-available": "Available", "state-offline": "Offline", - "update": "Last updated:", "public-key": "Remote visor public key", "no-elements": "Currently there are no elements to show. Please try again later.", "no-elements-for-filters": "There are no elements that meet the filter criteria.", @@ -309,8 +308,6 @@ "filter-dialog": { "title": "Filters", - "state": "The state must be", - "state-no-filter": "Do not filter", "location": "The location must contain", "pub-key": "The public key must contain", "apply": "Apply" From 500b5af7a6e154d0ae7ecd1fc0aaa8f717e2e1e9 Mon Sep 17 00:00:00 2001 From: Senyoret1 <34079003+Senyoret1@users.noreply.github.com> Date: Fri, 1 May 2020 12:45:56 -0400 Subject: [PATCH 4/6] Small improvements for the proxy discovery UI --- .../skysocks-client-settings.component.html | 4 ---- .../src/app/services/proxy-discovery.service.ts | 4 +++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.html b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.html index 9fb08609fe..c765b95d31 100644 --- a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.html +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.html @@ -89,10 +89,6 @@ {{ part }}
-
- {{ 'apps.skysocks-client-settings.port' | translate }} - {{ proxy.port }} -
{{ 'apps.skysocks-client-settings.location' | translate }}   diff --git a/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts b/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts index 63b7bdb2da..73e9261e8a 100644 --- a/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts +++ b/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts @@ -17,7 +17,9 @@ export class ProxyDiscoveryService { * URL of the proxy discovery service. While in dev mode the url is managed by the * dev server proxy. */ - private readonly discoveryServiceUrl = environment.production ? 'http://proxy.discovery.skywire.cc/api' : '/discovery-api'; + private readonly discoveryServiceUrl = environment.production ? + (window.location.protocol + '//proxy.discovery.skywire.cc/api') : + '/discovery-api'; constructor( private http: HttpClient, From dd8b61d5cea1e40bafce10432c01333179ca9e07 Mon Sep 17 00:00:00 2001 From: Senyoret1 <34079003+Senyoret1@users.noreply.github.com> Date: Wed, 12 Aug 2020 12:05:17 -0400 Subject: [PATCH 5/6] Fixes for configuring the skysocks-client app using the manager --- .../skysocks-client-settings.component.scss | 2 +- .../skysocks-client-settings.component.ts | 2 +- .../src/app/services/proxy-discovery.service.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss index 161f4fa11d..21008b5443 100644 --- a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss @@ -51,7 +51,7 @@ form { } .blue-part { - color: $blue; + color: $blue-medium; } } diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts index fc5c68cc94..a61b69b4f0 100644 --- a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.ts @@ -311,7 +311,7 @@ export class SkysocksClientSettingsComponent implements OnInit, OnDestroy { } ]; - SelectOptionComponent.openDialog(this.dialog, options).afterClosed().subscribe((selectedOption: number) => { + SelectOptionComponent.openDialog(this.dialog, options, 'common.options').afterClosed().subscribe((selectedOption: number) => { if (selectedOption === 1) { this.saveChanges(historyEntry.key, historyEntry.enteredManually, historyEntry.location, historyEntry.note); } else if (selectedOption === 2) { diff --git a/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts b/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts index 73e9261e8a..0edd689ddc 100644 --- a/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts +++ b/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts @@ -18,7 +18,7 @@ export class ProxyDiscoveryService { * dev server proxy. */ private readonly discoveryServiceUrl = environment.production ? - (window.location.protocol + '//proxy.discovery.skywire.cc/api') : + (window.location.protocol + '//service.discovery.skycoin.com/api/services?type=proxy') : '/discovery-api'; constructor( @@ -31,7 +31,7 @@ export class ProxyDiscoveryService { getProxies(): Observable { const response: ProxyDiscoveryEntry[] = []; - return this.http.get(this.discoveryServiceUrl + '/proxies').pipe( + return this.http.get(this.discoveryServiceUrl).pipe( // In case of error, retry. retryWhen(errors => errors.pipe(delay(4000))), map((result: any[]) => { From 60cc41b403445c1464540c4e869455ea10753660 Mon Sep 17 00:00:00 2001 From: Senyoret1 <34079003+Senyoret1@users.noreply.github.com> Date: Thu, 13 Aug 2020 10:43:19 -0400 Subject: [PATCH 6/6] Additional fixes for configuring the skysocks-client app using the manager --- .../skysocks-client-settings.component.scss | 2 +- .../src/app/services/proxy-discovery.service.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss index 21008b5443..fa4bdc06cf 100644 --- a/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss +++ b/static/skywire-manager-src/src/app/components/pages/node/apps/node-apps/skysocks-client-settings/skysocks-client-settings.component.scss @@ -22,7 +22,7 @@ form { } .list-button { - border-bottom: solid 1px $separator; + border-bottom: solid 1px $grey-separator; .filter-button-content { padding: 15px 0px; diff --git a/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts b/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts index 0edd689ddc..5deac4ed56 100644 --- a/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts +++ b/static/skywire-manager-src/src/app/services/proxy-discovery.service.ts @@ -17,9 +17,7 @@ export class ProxyDiscoveryService { * URL of the proxy discovery service. While in dev mode the url is managed by the * dev server proxy. */ - private readonly discoveryServiceUrl = environment.production ? - (window.location.protocol + '//service.discovery.skycoin.com/api/services?type=proxy') : - '/discovery-api'; + private readonly discoveryServiceUrl = 'https://service.discovery.skycoin.com/api/services?type=proxy'; constructor( private http: HttpClient,