Skip to content

Use WebSocket to get global settings #2008

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

Merged
merged 33 commits into from
Feb 26, 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
6 changes: 3 additions & 3 deletions .prow.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
presubmits:
- name: pull-dashboard-headless
- name: pull-dashboard-test-headless
always_run: true
decorate: true
clone_uri: "ssh://[email protected]/kubermatic/dashboard-v2.git"
Expand All @@ -24,7 +24,7 @@ presubmits:
name: kubermatic-codecov
key: token

- name: pull-dashboard-e2e
- name: pull-dashboard-test-e2e
always_run: true
decorate: true
clone_uri: "ssh://[email protected]/kubermatic/dashboard-v2.git"
Expand All @@ -48,7 +48,7 @@ presubmits:
- image: quay.io/kubermatic/e2e-kind-cypress:v1.1.1
command:
- make
- run-e2e-ci-v2
- run-e2e-ci
securityContext:
privileged: true
resources:
Expand Down
3 changes: 0 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ test-headless: install
./hack/upload-coverage.sh

run-e2e-ci: install
./hack/e2e/run_ci_e2e_test.sh

run-e2e-ci-v2: install
./hack/e2e/ci-e2e.sh

dist: install
Expand Down
6 changes: 3 additions & 3 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "kubermatic:build",
"proxyConfig": "./proxy.conf.json",
"proxyConfig": "./proxy.conf.js",
"port": 8000,
"hmrWarning": false
},
Expand All @@ -114,7 +114,7 @@
"browserTarget": "kubermatic:build:production"
},
"local": {
"proxyConfig": "./proxy-local.conf.json"
"proxyConfig": "./proxy-local.conf.js"
},
"e2e": {
"hmr": false,
Expand All @@ -123,7 +123,7 @@
"e2e-local": {
"hmr": false,
"browserTarget": "kubermatic:build:e2e-local",
"proxyConfig": "./proxy-local.conf.json"
"proxyConfig": "./proxy-local.conf.js"
}
}
},
Expand Down
57 changes: 0 additions & 57 deletions hack/e2e/run_ci_e2e_test.sh

This file was deleted.

13 changes: 13 additions & 0 deletions proxy-local.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const PROXY_CONFIG = [
{
context: [
"/api/**",
],
target: "http://localhost:8080",
changeOrigin: true,
secure: false,
ws: true,
}
];

module.exports = PROXY_CONFIG;
7 changes: 0 additions & 7 deletions proxy-local.conf.json

This file was deleted.

16 changes: 16 additions & 0 deletions proxy.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const PROXY_CONFIG = [
{
context: [
"/api/**",
],
target: "https://dev.kubermatic.io",
changeOrigin: true,
headers: {
'Origin': 'https://dev.kubermatic.io',
},
secure: false,
ws: true,
}
];

module.exports = PROXY_CONFIG;
7 changes: 0 additions & 7 deletions proxy.conf.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
target="_blank"
mat-flat-button
[disabled]="!isClusterRunning || (!isEditEnabled() && isOpenshiftCluster())"
*ngIf="(settings.adminSettings | async).enableDashboard">
*ngIf="(settings.adminSettings | async)?.enableDashboard">
{{getConnectName()}}
</a>
</div>
Expand Down
54 changes: 28 additions & 26 deletions src/app/core/services/settings/settings.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {HttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {iif, merge, Observable, of, Subject, timer} from 'rxjs';
import {catchError, map, shareReplay, switchMap} from 'rxjs/operators';
import {BehaviorSubject, iif, merge, Observable, of, Subject, timer} from 'rxjs';
import {catchError, delay, map, retryWhen, shareReplay, switchMap, tap} from 'rxjs/operators';
import {webSocket} from 'rxjs/webSocket';

import {Auth} from '..';
import {environment} from '../../../../environments/environment';
Expand Down Expand Up @@ -31,15 +32,19 @@ const DEFAULT_ADMIN_SETTINGS: AdminSettings = {
enableOIDCKubeconfig: false,
};

@Injectable()
@Injectable({
providedIn: 'root',
})
export class SettingsService {
private readonly restRoot: string = environment.restRoot;
private readonly restRoot = environment.restRoot;
private readonly wsProtocol = window.location.protocol.replace('http', 'ws');
private readonly wsRoot = `${this.wsProtocol}//${window.location.host}/${this.restRoot}/ws`;
private _userSettings$: Observable<UserSettings>;
private _userSettingsRefresh$: Subject<any> = new Subject();
private _adminSettings$: Observable<AdminSettings>;
private _adminSettingsRefresh$: Subject<any> = new Subject();
private _userSettingsRefresh$ = new Subject();
private readonly _adminSettings$ = new BehaviorSubject(DEFAULT_ADMIN_SETTINGS);
private _adminSettingsWatch$: Observable<AdminSettings>;
private _admins$: Observable<AdminEntity[]>;
private _adminsRefresh$: Subject<any> = new Subject();
private _adminsRefresh$ = new Subject();
private _refreshTimer$ = timer(0, this._appConfigService.getRefreshTimeBase() * 5);

constructor(
Expand Down Expand Up @@ -86,28 +91,29 @@ export class SettingsService {
}

get adminSettings(): Observable<AdminSettings> {
if (!this._adminSettings$) {
this._adminSettings$ =
merge(this._refreshTimer$, this._adminSettingsRefresh$)
.pipe(switchMap(
() =>
iif(() => this._auth.authenticated(), this._getAdminSettings(true), of(DEFAULT_ADMIN_SETTINGS))))
.pipe(map(settings => this._defaultAdminSettings(settings)))
.pipe(shareReplay({refCount: true, bufferSize: 1}));
// Subscribe to websocket and proxy all the settings updates coming from the API to the subject that is
// exposed in this method. Thanks to that it is possible to have default value and retry mechanism that
// will run in the background if connection will fail. Subscription to the API should happen only once.
// Behavior subject is used internally to always emit last value when subscription happens.
if (!this._adminSettingsWatch$) {
const webSocket$ =
webSocket<AdminSettings>(`${this.wsRoot}/admin/settings`)
.asObservable()
.pipe(retryWhen(
// Display error in the console for debugging purposes, otherwise it would be ignored.
// tslint:disable-next-line:no-console
errors => errors.pipe(tap(console.debug), delay(this._appConfigService.getRefreshTimeBase() * 3))));
this._adminSettingsWatch$ = iif(() => this._auth.authenticated(), webSocket$, of(DEFAULT_ADMIN_SETTINGS));
this._adminSettingsWatch$.subscribe(settings => this._adminSettings$.next(this._defaultAdminSettings(settings)));
}

return this._adminSettings$;
}

get defaultAdminSettings(): AdminSettings {
return DEFAULT_ADMIN_SETTINGS;
}

private _getAdminSettings(defaultOnError = false): Observable<AdminSettings> {
const url = `${this.restRoot}/admin/settings`;
const observable = this._httpClient.get<AdminSettings>(url);
return defaultOnError ? observable.pipe(catchError(() => of(DEFAULT_ADMIN_SETTINGS))) : observable;
}

private _defaultAdminSettings(settings: AdminSettings): AdminSettings {
if (!settings) {
return DEFAULT_ADMIN_SETTINGS;
Expand All @@ -120,10 +126,6 @@ export class SettingsService {
return settings;
}

refreshAdminSettings(): void {
this._adminSettingsRefresh$.next();
}

patchAdminSettings(patch: any): Observable<AdminSettings> {
const url = `${this.restRoot}/admin/settings`;
return this._httpClient.patch<AdminSettings>(url, patch);
Expand Down
9 changes: 6 additions & 3 deletions src/app/settings/admin/admin-settings.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,18 @@ export class AdminSettingsComponent implements OnInit, OnChanges, OnDestroy {

this._settingsService.adminSettings.pipe(takeUntil(this._unsubscribe)).subscribe(settings => {
if (!_.isEqual(settings, this.apiSettings)) {
if (this.apiSettings) {
if (this._shouldDisplayUpdateNotification()) {
this._notificationService.success('Successfully applied external settings update');
}
this._applySettings(settings);
}
});

this._settingsChange.pipe(debounceTime(1000))
this._settingsChange.pipe(debounceTime(500))
.pipe(takeUntil(this._unsubscribe))
.pipe(switchMap(() => this._settingsService.patchAdminSettings(this._getPatch())))
.subscribe(settings => {
this._applySettings(settings);
this._settingsService.refreshAdminSettings();
});

this._settingsService.userSettings.pipe(takeUntil(this._unsubscribe)).subscribe(settings => {
Expand All @@ -88,6 +87,10 @@ export class AdminSettingsComponent implements OnInit, OnChanges, OnDestroy {
this._unsubscribe.complete();
}

private _shouldDisplayUpdateNotification(): boolean {
return this.apiSettings && this.apiSettings !== this._settingsService.defaultAdminSettings;
}

private _applySettings(settings: AdminSettings): void {
this.apiSettings = settings;
this.settings = _.cloneDeep(this.apiSettings);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges} from '@angular/core';
import {AbstractControl, FormArray, FormBuilder, FormGroup, Validators} from '@angular/forms';
import * as _ from 'lodash';

Expand All @@ -9,7 +9,7 @@ import {CustomLink, CustomLinkLocation} from '../../../shared/utils/custom-link-
templateUrl: './custom-links-form.component.html',
styleUrls: ['./custom-links-form.component.scss'],
})
export class CustomLinksFormComponent implements OnInit {
export class CustomLinksFormComponent implements OnInit, OnChanges {
@Input() customLinks: CustomLink[] = [];
@Output() customLinksChange = new EventEmitter<CustomLink[]>();
@Input() apiCustomLinks: CustomLink[] = [];
Expand All @@ -22,6 +22,16 @@ export class CustomLinksFormComponent implements OnInit {
}

ngOnInit(): void {
this._buildForm();
}

ngOnChanges(changes: SimpleChanges): void {
if (changes.customLinks.currentValue !== changes.customLinks.previousValue) {
this._buildForm();
}
}

private _buildForm(): void {
this.form = this._formBuilder.group({customLinks: this._formBuilder.array([])});
this.customLinks.forEach(
customLink => this._addCustomLink(customLink.label, customLink.url, customLink.icon, customLink.location));
Expand Down
5 changes: 2 additions & 3 deletions src/app/wizard/wizard.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ export class WizardComponent implements OnInit, OnDestroy {
this._settingsService.adminSettings.pipe(first()).subscribe(
settings => this.addNodeData.count = settings.defaultNodeCount);

this._settingsService.adminSettings.pipe(takeUntil(this._unsubscribe)).subscribe(settings => {
this.settings = settings;
});
this._settingsService.adminSettings.pipe(takeUntil(this._unsubscribe))
.subscribe(settings => this.settings = settings);

this.updateSteps();
this._projectService.selectedProject.pipe(takeUntil(this._unsubscribe))
Expand Down
2 changes: 0 additions & 2 deletions src/environments/environment.e2e.local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ export const environment = {
customCSS: '../../assets/custom/style.css',
refreshTimeBase: 1000, // Unit: ms
restRoot: 'api/v1',
restRootV3: 'api/v3',
digitalOceanRestRoot: 'https://api.digitalocean.com/v2',
oidcProviderUrl: 'http://dex.oauth:5556/dex/auth',
oidcConnectorId: 'local',
animations: false,
Expand Down
2 changes: 0 additions & 2 deletions src/environments/environment.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ export const environment = {
customCSS: '../../assets/custom/style.css',
refreshTimeBase: 1000, // Unit: ms
restRoot: 'api/v1',
restRootV3: 'api/v3',
digitalOceanRestRoot: 'https://api.digitalocean.com/v2',
oidcProviderUrl: 'https://dev.kubermatic.io/dex/auth',
oidcConnectorId: 'local',
animations: false,
Expand Down
2 changes: 0 additions & 2 deletions src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ export const environment = {
customCSS: '../assets/custom/style.css',
refreshTimeBase: 1000, // Unit: ms
restRoot: '/api/v1',
restRootV3: '/api/v3',
digitalOceanRestRoot: 'https://api.digitalocean.com/v2',
oidcProviderUrl: window.location.protocol + '//' + window.location.host + '/dex/auth',
oidcConnectorId: null,
animations: true,
Expand Down
2 changes: 0 additions & 2 deletions src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ export const environment = {
customCSS: '../../assets/custom/style.css',
refreshTimeBase: 1000, // Unit: ms
restRoot: 'api/v1',
restRootV3: 'api/v3',
digitalOceanRestRoot: 'https://api.digitalocean.com/v2',
oidcProviderUrl: 'https://dev.kubermatic.io/dex/auth',
oidcConnectorId: null,
animations: true,
Expand Down